tus_background_upload

CI

TUS resumable uploads that keep running while your app is backgrounded or the device is locked — including uploads that take longer than Android's ~9-minute background cap.

Pure-Dart TUS protocol layer on top of background_downloader's native transports (iOS background URLSession, Android WorkManager with foreground-service promotion). No native code of its own.

Features

  • TusUploader — one state machine per file: confirmed-offset tracking, whole-file progress (a resumed upload continues at the fraction the user last saw), and pause / resume / cancel:
    • pause() stops the transfer but keeps the server-side offset;
    • resume(...) re-reads the offset (TUS HEAD) and continues from there;
    • cancel() completes the upload(...) future with TusUploadCancelledException.
  • createTusUpload(...) — the TUS creation extension (POST with Upload-Length / Upload-MetadataLocation), for servers like tusd that don't hand out upload URLs some other way.
  • configureBackgroundUploads(...) — one call at app start that lifts the platform limits: Android runInForeground (foreground service + notification, so WorkManager doesn't kill the task after ~9 minutes) and a configurable iOS resourceTimeout (default 8 h).
  • BackgroundMultipartUploader — plain multipart uploads (images, documents) over the same background-safe native transports.
  • TusTransport — the wire seam; inject a fake in tests, or swap the transport entirely.

Quick start

import 'package:tus_background_upload/tus_background_upload.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Once at app start — enables long-running background transfers.
  await configureBackgroundUploads(
    notificationTitle: 'Uploading',
    notificationBody: 'Your file keeps uploading in the background',
  );

  runApp(const MyApp());
}

Future<void> uploadVideo(File file) async {
  // Android 13+ needs the notification permission for the >9-minute guarantee.
  await requestBackgroundUploadNotificationPermission();

  // 1. TUS creation — skip if your backend already hands out upload URLs.
  final Uri uploadUrl = await createTusUpload(
    creationEndpoint: Uri.parse('https://tusd.tusdemo.net/files/'),
    fileLength: await file.length(),
    metadata: <String, String>{'filename': 'video.mp4'},
  );

  // 2. The transfer — runs natively, survives backgrounding and device lock.
  final TusUploader uploader = TusUploader(transport: BackgroundDownloaderTusTransport());
  await uploader.upload(
    endpoint: uploadUrl,
    headers: const <String, String>{},
    filePath: file.path,
    fileLength: await file.length(),
    onProgress: (double progress) => print('${(progress * 100).toStringAsFixed(0)}%'),
  );
}

Pause and resume:

await uploader.pause();            // server keeps the received bytes
await uploader.resume(             // HEADs the offset, PATCHes the rest
  endpoint: uploadUrl,
  headers: const <String, String>{},
  filePath: file.path,
  fileLength: fileLength,
  onProgress: onProgress,
);

Platform setup

Android

For uploads that must outlive the ~9-minute background window, add to android/app/src/main/AndroidManifest.xml:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

and request the notification permission at runtime (requestBackgroundUploadNotificationPermission()). Without the grant, uploads still run but Android may stop them after ~9 minutes in the background.

iOS

Nothing to declare — transfers run on a background URLSession out of the box. configureBackgroundUploads sets the resource timeout (default 8 h) so very large files aren't abandoned early.

Example

The example/ app picks a file and uploads it to any TUS server (default: the public tusd demo server) with a progress bar and pause / resume / cancel buttons — handy for verifying the background behavior on a real device: start a big upload, background the app for 10+ minutes, watch it finish.

Testing your own code

TusUploader takes any TusTransport, so unit tests inject a fake transport and drive offsets/progress by hand — see test/tus_uploader_test.dart.