another_tus_client 3.2.6
another_tus_client: ^3.2.6 copied to clipboard
A web compatible tus client for dart allowing resumable uploads using the tus protocol.
Another TUS Client #
A Dart client for resumable file uploads using the TUS protocol.
Forked from tus_client_dart.
tus is an HTTP‑based protocol for resumable file uploads.
It enables interruption (intentional or accidental) and later resumption of uploads without needing to restart from the beginning.
Table of Contents #
Usage Examples #
1. Creating a Client #
import 'package:another_tus_client/another_tus_client.dart';
import 'package:cross_file/cross_file.dart';
final file = XFile("/path/to/my/pic.jpg");
final client = TusClient(
file, // Must be an XFile
store: TusMemoryStore(), // Will not persist through device restarts. For persistent URL storage in memory see below
maxChunkSize: 6 * 1024 * 1024, // 6MB chunks
retries: 5,
retryScale: RetryScale.exponential,
retryInterval: 2,
debug: true, // Will debug both the store and the client
);
2. Starting an Upload #
await client.upload(
uri: Uri.parse("https://your-tus-server.com/files/"),
onStart: (TusClient client, Duration? estimate) {
print("Upload started; estimated time: ${estimate?.inSeconds} seconds");
},
onProgress: (double progress, Duration estimate) {
print("Progress: ${progress.toStringAsFixed(1)}%, estimated time: ${estimate.inSeconds} seconds");
},
onComplete: () {
print("Upload complete!");
print("File URL: ${client.uploadUrl}");
},
headers: {"Authorization": "Bearer your_token"},
metadata: {"cacheControl": "3600"},
measureUploadSpeed: true,
preventDuplicates: true, // NEW: Prevents creating duplicate uploads of the same file
);
3. Pausing an Upload #
Upload will pause after the current chunk finishes. For example:
print("Pausing upload...");
await client.pauseUpload();
4. Resuming an Upload #
If the upload has been paused, you can resume using:
// Resume with the same callbacks as original upload
await client.resumeUpload();
// Resume with a new progress callback
await client.resumeUpload(
onProgress: (progress, estimate) {
print("New progress handler: $progress%");
}
);
// Clear the progress callback while keeping others
await client.resumeUpload(
clearProgressCallback: true
);
// Replace some callbacks and clear others
await client.resumeUpload(
onComplete: () => print("New completion handler"),
clearProgressCallback: true
);
// Clear all callbacks
await client.clearAllCallbacks();
await client.resumeUpload();
5. Canceling an Upload #
Cancel the current upload and remove any saved state:
final result = await client.cancelUpload(); // Returns true when successful
if (result) {
print("Upload canceled successfully.");
}
6. Using TusFileStore (Native Platforms) #
On mobile/desktop, you can persist the upload progress to the file system.
import 'package:path_provider/path_provider.dart';
import 'dart:io';
final tempDir = await getTemporaryDirectory();
final tempDirectory = Directory('${tempDir.path}/${file.name}_uploads');
if (!tempDirectory.existsSync()) {
tempDirectory.createSync(recursive: true);
}
final client = TusClient(
file,
store: TusFileStore(tempDirectory),
);
await client.upload(uri: Uri.parse("https://your-tus-server.com/files/"));
7. Using TusIndexedDBStore (Web) #
For web applications, use IndexedDB for persistent upload state:
import 'package:flutter/foundation.dart' show kIsWeb;
final store = kIsWeb ? TusIndexedDBStore() : TusMemoryStore();
final client = TusClient(
file,
store: store,
);
await client.upload(uri: Uri.parse("https://your-tus-server.com/files/"));
8. File Selection on Web #
For web applications, you have two main options for handling files:
Option 1: Using Any XFile with Loaded Bytes
import 'package:file_picker/file_picker.dart';
final result = await FilePicker.platform.pickFiles(
withData: true, // Load bytes into memory. Works for small files
);
if (result == null) {
return null;
}
final fileWithBytes = result.files.first.xFile; // This returns an XFile with bytes
// Create client with any XFile that has bytes loaded
final client = TusClient(
fileWithBytes, // Any XFile with bytes already loaded
store: TusMemoryStore(), //This TusMemoryStore doesn't persist on reboots.
);
await client.upload(uri: Uri.parse("https://tus.example.com/files"));
Option 2: Using pickWebFilesForUpload()
This is a built-in method that will open a file picker on web and convert the files to a streamable XFile using Blob.
final result = await pickWebFilesForUpload(
allowMultiple: true,
acceptedFileTypes: ['*']
)
if (result == null) {
return null;
}
// Create client with any XFile that has bytes loaded
final client = TusClient(
result.first, // Streaming ready XFile
store: TusMemoryStore(),
);
await client.upload(uri: Uri.parse("https://tus.example.com/files"));
9. Using TusUploadManager #
The TusUploadManager provides a convenient way to manage multiple uploads with features like automatic queuing, status tracking, and batch operations.
Creating the Upload Manager
import 'package:another_tus_client/another_tus_client.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
// Create a persistent store for upload state
final store = kIsWeb ? TusIndexedDBStore() : TusFileStore(await getUploadDirectory());
// Initialize the manager
final uploadManager = TusUploadManager(
serverUrl: Uri.parse("https://your-tus-server.com/files/"),
store: store,
maxConcurrentUploads: 3, // Control how many uploads run simultaneously
autoStart: true, // Start uploads as soon as they're added (default)
measureUploadSpeed: true, // Estimate upload time
retries: 3, // Retry failed uploads 3 times
retryScale: RetryScale.exponential,
retryInterval: 2, // Wait 2, 4, 8 seconds between retries
preventDuplicates: true, // Prevent creating duplicate uploads
debug: true, // Will debug manager, client and store
);
Adding Files to Upload
// Add a single file. Returns a custom ID for this upload
final uploadId1 = await uploadManager.addUpload(
file1,
metadata: {
"bucketName": "user_files",
"cacheControl": "3600",
"contentType": file1.mimeType ?? "application/octet-stream"
},
headers: {
"x-custom-header": "value" // Add upload-specific headers
}
);
// Add another file with different settings
final uploadId2 = await uploadManager.addUpload(
file2,
metadata: {"bucketName": "images"}
);
Listening to Upload Events
// Listen to upload status changes and progress updates
uploadManager.uploadEvents.listen((UploadEvent event) {
final upload = event.upload;
final type = event.eventType;
print("Upload ID: ${upload.id}");
print("Event: ${type}"); // start, resume, pause, progress, complete, error, cancel, add
print("Status: ${upload.status}"); // ready, uploading, paused, completed, failed, cancelled
print("Progress: ${upload.progress}%");
print("Estimated time: ${upload.estimate.inSeconds} seconds");
if (upload.status == UploadStatus.completed) {
print("Upload URL: ${upload.client.uploadUrl}");
} else if (upload.status == UploadStatus.failed) {
print("Error: ${upload.error}");
}
});
Controlling Individual Uploads
// Pause a specific upload
await uploadManager.pauseUpload(uploadId1);
// Resume a paused upload
await uploadManager.resumeUpload(uploadId1);
// Cancel an upload
await uploadManager.cancelUpload(uploadId2);
// Check for specific upload
final upload = uploadManager.getUpload(uploadId1);
if (upload != null && upload.status == UploadStatus.paused) {
// Check if it's resumable
final isResumable = await upload.client.isResumable();
print("Can resume: $isResumable");
}
Batch Operations
// Pause all active uploads
await uploadManager.pauseAll();
// Resume all paused uploads
await uploadManager.resumeAll();
// Cancel all uploads
await uploadManager.cancelAll();
// Get all uploads
final allUploads = uploadManager.getAllUploads();
print("Total uploads: ${allUploads.length}");
Cleanup
// Clean up resources when done
@override
void dispose() {
uploadManager.dispose();
super.dispose();
}