background_downloader 1.5.0 copy "background_downloader: ^1.5.0" to clipboard
background_downloader: ^1.5.0 copied to clipboard

A background file downloader for iOS and Android. Define where to get your file from, where to store it, enqueue your task and monitor it

A background file downloader for iOS and Android #

Define where to get your file from, where to store it, and how you want to monitor the download, and the background loader will ensure this is done in a responsible way using native platform background downloaders. background_downloader uses URLSessions on iOS and DownloadWorker on Android, so tasks will complete also when your app is in the background.

Concepts and basic usage #

A download is defined by a BackgroundDownloadTask object that contains the download instructions, and updates related to that task are passed on to a stream you can listen to, or alternatively to callback functions that you register.

Using an event listener #

For simple downloads you listen to events from the downloader, and process those. For example, the following creates a listener to monitor status and progress updates for downloads, and then enqueues a task as an example:

  FileDownloader.initialize();  // initialize before starting to listen
  final subscription = FileDownloader.updates.listen((event) {
      if (event is BackgroundDownloadStatusEvent) {
        print('Status update for ${event.task} with status ${event.status}');
      } else if (event is BackgroundDownloadProgressEvent) {
        print('Progress update for ${event.task} with progress ${event.progress}');
    });
    // initate a download
    final successFullyEnqueued = await FileDownloader.enqueue(
      BackgroundDownloadTask(url: 'https://google.com', filename: 'google.html'));
    // status update events will be sent to your subscription listener

Note that successFullyEnqueued only refers to the enqueueing of the download task, not its result, which must be monitored via the listener. It will receive an update with status DownloadTaskStatus.running, followed by a status update with the result (e.g. DownloadTaskStatus.complete or DownloadTaskStatus.failed).

You can start your subscription in a convenient place, like a widget's initState, and don't forget to cancel your subscription to the stream using subscription.cancel(). Note the stream can only be listened to once: to listen again, first call FileDownloader.initialize().

Using callbacks #

For more complex downloads (e.g. if you want different handlers for different groups of downloads - see below) you can register a callback for status updates, and/or a callback for progress updates.

The DownloadStatusCallback receives the BackgroundDownloadTask and the updated DownloadTaskStatus, so a simple callback function is:

void downloadStatusCallback(
    BackgroundDownloadTask task, DownloadTaskStatus status) {
  print('downloadStatusCallback for $task with status $status');
}

The DownloadProgressCallback receives the BackgroundDownloadTask and progess as a double, so a simple callback function is:

void downloadProgressCallback(BackgroundDownloadTask task, double progress) {
  print('downloadProgressCallback for $task with progress $progress');
}

A basic file download with just status monitoring (no progress) then requires initialization to register the callback, and a call to enqueue to start the download:

  FileDownloader.initialize(downloadStatusCallback: downloadStatusCallback);
  final successFullyEnqueued = await FileDownloader.enqueue(
      BackgroundDownloadTask(url: 'https://google.com', filename: 'google.html'));

If you register a callback for a type of task, updates are provided only through that callback and will not be posted on the updates stream.

Location of the downloaded file #

The filename of the task refers to the filename without directory. To store the task in a specific directory, add the directory parameter to the task. That directory is relative to the base directory, so cannot start with a /. By default, the base directory is the directory returned by the call to getApplicationDocumentsDirectory(), but this can be changed by also passing a baseDirectory parameter (BaseDirectory.temporary for the directory returned by getTemporaryDirectory() and BaseDirectory.applicationSupport for the directory returned by getApplicationSupportDirectory() which is only supported on iOS).

So, to store a file named 'testfile.txt' in the documents directory, subdirectory 'my/subdir', define the task as follows:

final task = BackgroundDownloadTask(
        url: 'https://google.com',
        filename: 'testfile.txt',
        directory: 'my/subdir');

To store that file in the temporary directory:

final task = BackgroundDownloadTask(
        url: 'https://google.com',
        filename: 'testfile.txt',
        directory: 'my/subdir',
        baseDirectory: BaseDirectory.temporary);

The downloader will only store the file upon success (so there will be no partial files saved), and if so, the destination is overwritten if it already exists, and all intermediate directories will be created if needed.

Note: the reason you cannot simply pass a full absolute directory path to the downloader is that the location of the app's documents directory may change between application starts (on iOS), and may therefore fail for downloads that complete while the app is suspended. You should therefore never store permanently, or hard-code, an absolute path.

Monitoring progress while downloading #

Status updates only report on start and finish of a download. To also monitor progress while the file is downloading, listen for BackgroundDownloadProgressEvent on the Filedownloader.updates stream (or register a DownloadProgressCallback) and add a progressUpdates parameter to the task:

    FileDownloader.initialize(
        downloadStatusCallback: downloadStatusCallback,
        downloadProgressCallback: downloadProgressCallback);
    final task = BackgroundDownloadTask(
        url: 'https://google.com',
        filename: 'google.html',
        progressUpdates:  // needed to also get progress updates
            DownloadTaskProgressUpdates.statusChangeAndProgressUpdates);
    final successFullyEnqueued = await FileDownloader.enqueue(task);

Progress updates will be sent periodically, not more than twice per second per task. If a task completes successfully you will receive a progress update with a progress value of 1.0. Failed tasks generate progress of -1, cancelled tasks -2 and notFound tasks -3.

Because you can use the progress value to derive task status, you can choose to not receive status updates by setting the progressUpdates parameter of a task to DownloadTaskProgressUpdates.progressUpdates (and you won't need to register a DownloadStatusCallback or listen for status updates). If you don't want to use any callbacks (and just check if the file exists after a while!) set the progressUpdates parameter of a task to DownloadTaskProgressUpdates.none.

If instead of using callbacks you are listening to the Filedownloader.updates stream, you can distinguish progress updates from status updates by testing the event's type (BackgroundDownloadStatusEvent or BackgroundDownloadProgressEvent) and handle it accordingly.

Simplified use #

Simplified use does not require you to register callbacks or listen to updates: you just call .download or .downloadBatch and wait for the result. Note that for simplified use, BackgroundDownloadTask fields group and progressUpdates should not be set, as they are used by the FileDownloader for these convenience methods, and may be overwritten.

Awaiting a download #

If status and progress monitoring is not required, use the convenience method download, which returns a Future that completes when the file download has completed or failed:

    final result = await FileDownloader.download(task);

The result will be a DownloadTaskStatus and should be checked for completion, failure etc.

Awaiting a batch download #

To download a batch of files and wait for completion, create a List of BackgroundDownloadTask objects and call downloadBatch:

   final result = await FileDownloader.downloadBatch(tasks);

The result is a BackgroundDownloadBatch object that contains the result for each task in .results. You can use .numSucceeded and .numFailed to check if all files in the batch downloaded successfully, and use .succeeded or .failed to iterate over successful or failed tasks within the batch - for example to report back, or to retry. If you want to get progress updates for the batch (in terms of how many files have been downloaded) then add a callback:

   final result = await FileDownloader.downloadBatch(tasks, (succeeded, failed) {
      print('$succeeded files succeeded, $failed have failed');
      print('Progress is ${(succeeded + failed) / tasks.length} %');
   });

The callback will be called upon completion of each task (whether successful or not), and will start with (0, 0) before any downloads start, so you can use that to start a progress indicator. Note that it is not possible to monitor download progress of individual files within the batch - you need to enqueue individual files to do that.

Advanced use #

Headers #

Optionally, headers can be added to the BackgroundDownloadTask, which will be added to the HTTP request. This may be useful for authentication, for example.

Metadata #

Also optionally, metaData can be added to the BackgroundDownloadTask (a String). Metadata is ignored by the downloader but may be helpful when receiving an update about the task.

Managing and monitoring tasks in the queue #

To manage or monitor tasks, use the following methods:

  • reset to reset the downloader by cancelling all ongoing download tasks
  • allTaskIds to get a list of taskId values of all tasks currently running (i.e. not in a final state)
  • allTasks to get a list of all tasks currently running (i.e. not in a final state)
  • cancelTasksWithIds to cancel all tasks with a taskId in the provided list of taskIds
  • taskForId to get the BackgroundDownloadTask for the given taskId, or null if not found. Only tasks that are running (ie. not in a final state) are guaranteed to be returned, but returning a task does not guarantee that it is running

Grouping tasks #

Because an app may require different types of downloads, and handle those differently, you can specify a group with your task, and register callbacks specific to each group. If no group is specified (as in the examples above), the default group named default is used. For example, to create and handle downloads for group 'bigFiles':

  FileDownloader.registerCallbacks(
        group: 'bigFiles'
        downloadStatusCallback: bigFilesDownloadStatusCallback,
        downloadProgressCallback: bigFilesDownloadProgressCallback);
  final task = BackgroundDownloadTask(
        group: 'bigFiles',
        url: 'https://google.com',
        filename: 'google.html',
        progressUpdates:
            DownloadTaskProgressUpdates.statusChangeAndProgressUpdates);
  final successFullyEnqueued = await FileDownloader.enqueue(task);

The methods initialize, registerCallBacks, reset, allTaskIds and allTasks all take an optional group parameter to target tasks in a specific group. Note that if tasks are enqueued with a group other than default, calling any of these methods without a group parameter will not affect/include those tasks - only the default tasks.

If you listen to the updates stream instead of using callbacks, you can test for the task's group field in your event listener, and process the event differently for different groups.

Initial setup for iOS #

On iOS, ensure that you have the Background Fetch capability enabled:

  • Select the Runner target in XCode
  • Select the Signing & Capabilities tab
  • Click the + icon to add capabilities
  • Select 'Background Modes'
  • Tick the 'Background Fetch' mode

Note that iOS by default requires all URLs to be https (and not http). See here for more details and how to address issues.

No setup is required for Android.

Known issue with Firebase plugin onBackgroundMessage handler on Android #

In some cases, the Firebase plugin may interfere with other plugins, including this one. If your download does not complete, and/or if you don't receive status or progress updates, please check this issue to see if it affects you, and use this fix as a work-around.

Limitations #

  • On Android, once started, a background download must complete within 8 minutes
  • On iOS, once enqueued, a background download must complete within 4 hours
  • On both platforms, downloads will not start without a network connection, and do not distinguish between metered (cellular) and unmetered (WiFi) connections
  • Redirects will be followed
  • Background downloads will not be retried upon failure
  • Background downloads are aggressively controlled by the native platform. You should therefore always assume that a task that was started may not complete, and may disappear without providing any status or progress update to indicate why. For example, if a user swipes your app up from the iOS App Switcher, all scheduled background downloads are terminated without notification
337
likes
0
points
54.6k
downloads

Publisher

verified publisherbbflight.com

Weekly Downloads

A background file downloader for iOS and Android. Define where to get your file from, where to store it, enqueue your task and monitor it

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter, logging

More

Packages that depend on background_downloader