multi_downloader 1.0.6

  • Readme
  • Changelog
  • Example
  • Installing
  • 71

Multi Downloader #

Pub package build status support

A plugin for creating and managing download tasks. Supports iOS and Android.

This plugin is based on WorkManager in Android and NSURLSessionDownloadTask in iOS to run download task in background mode.

iOS integration #

Required configuration: #

Note: following steps requires to open your ios project in Xcode.

  • Enable background mode.
  • Add sqlite library.

  • Configure AppDelegate:

Objective-C:

/// AppDelegate.h
#import <Flutter/Flutter.h>
#import <UIKit/UIKit.h>

@interface AppDelegate : FlutterAppDelegate

@end
// AppDelegate.m
#include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h"
#include "FlutterDownloaderPlugin.h"

@implementation AppDelegate

void registerPlugins(NSObject<FlutterPluginRegistry>* registry) {   
  if (![registry hasPlugin:@"FlutterDownloaderPlugin"]) {
     [FlutterDownloaderPlugin registerWithRegistrar:[registry registrarForPlugin:@"FlutterDownloaderPlugin"]];
  }
}

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  [GeneratedPluginRegistrant registerWithRegistry:self];
  [FlutterDownloaderPlugin setPluginRegistrantCallback:registerPlugins];
  // Override point for customization after application launch.
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

@end

Or Swift:

import UIKit
import Flutter
import multi_downloader

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    GeneratedPluginRegistrant.register(with: self)
    FlutterDownloaderPlugin.setPluginRegistrantCallback(registerPlugins)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

private func registerPlugins(registry: FlutterPluginRegistry) { 
    if (!registry.hasPlugin("FlutterDownloaderPlugin")) {
       FlutterDownloaderPlugin.register(with: registry.registrar(forPlugin: "FlutterDownloaderPlugin"))
    }
}

Optional configuration: #

  • Support HTTP request: if you want to download file with HTTP request, you need to disable Apple Transport Security (ATS) feature. There're two options:
  1. Disable ATS for a specific domain only: (add following codes to your Info.plist file)
<key>NSAppTransportSecurity</key>
<dict>
  <key>NSExceptionDomains</key>
  <dict>
    <key>www.yourserver.com</key>
    <dict>
      <!-- add this key to enable subdomains such as sub.yourserver.com -->
      <key>NSIncludesSubdomains</key>
      <true/>
      <!-- add this key to allow standard HTTP requests, thus negating the ATS -->
      <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
      <true/>
      <!-- add this key to specify the minimum TLS version to accept -->
      <key>NSTemporaryExceptionMinimumTLSVersion</key>
      <string>TLSv1.1</string>
    </dict>
  </dict>
</dict>
  1. Completely disable ATS: (add following codes to your Info.plist file)
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key><true/>
</dict>
  • Configure maximum number of concurrent tasks: the plugin allows 3 download tasks running at a moment by default (if you enqueue more than 3 tasks, there're only 3 tasks running, other tasks are put in pending state). You can change this number by adding following codes to your Info.plist file.
<!-- changes this number to configure the maximum number of concurrent tasks -->
<key>FDMaximumConcurrentTasks</key>
<integer>5</integer>
  • Localize notification messages: the plugin will send a notification message to notify user in case all files are downloaded while your application is not running in foreground. This message is English by default. You can localize this message by adding and localizing following message in Info.plist file. (you can find the detail of Info.plist localization in this link)
<key>FDAllFilesDownloadedMessage</key>
<string>All files have been downloaded</string>

Note:

  • This plugin only supports save files in NSDocumentDirectory

Android integration #

Required configuration: #

  • If your project is running on Flutter versions prior v1.12, have a look at this document to configure your Android project.

  • From Flutter v1.12 with Android v2 embedding there's no additional configurations required to work with background isolation in Android (but you need to setup your project properly. See upgrading pre 1.12 Android projects)

  • In order to handle click action on notification to open the downloaded file on Android, you need to add some additional configurations. Add the following codes to your AndroidManifest.xml:

<provider
    android:name="vn.hunghd.flutterdownloader.DownloadedFileProvider"
    android:authorities="${applicationId}.multi_downloader.provider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/provider_paths"/>
</provider>

Note:

  • You have to save your downloaded files in external storage (where the other applications have permission to read your files)
  • The downloaded files are only able to be opened if your device has at least an application that can read these file types (mp3, pdf, etc)

Optional configuration: #

  • Configure maximum number of concurrent tasks: the plugin depends on WorkManager library and WorkManager depends on the number of available processor to configure the maximum number of tasks running at a moment. You can setup a fixed number for this configuration by adding following codes to your AndroidManifest.xml:
 <provider
     android:name="androidx.work.impl.WorkManagerInitializer"
     android:authorities="${applicationId}.workmanager-init"
     android:enabled="false"
     android:exported="false" />

 <provider
     android:name="vn.hunghd.flutterdownloader.FlutterDownloaderInitializer"
     android:authorities="${applicationId}.flutter-downloader-init"
     android:exported="false">
     <!-- changes this number to configure the maximum number of concurrent tasks -->
     <meta-data
         android:name="vn.hunghd.flutterdownloader.MAX_CONCURRENT_TASKS"
         android:value="5" />
 </provider>
  • Localize notification messages: you can localize notification messages of download progress by localizing following messages. (you can find the detail of string localization in Android in this link)
<string name="multi_downloader_notification_started">Download started</string>
<string name="multi_downloader_notification_in_progress">Download in progress</string>
<string name="multi_downloader_notification_canceled">Download canceled</string>
<string name="multi_downloader_notification_failed">Download failed</string>
<string name="multi_downloader_notification_complete">Download complete</string>
<string name="multi_downloader_notification_paused">Download paused</string>
  • PackageInstaller: in order to open APK files, your application needs REQUEST_INSTALL_PACKAGES permission. Add following codes in your AndroidManifest.xml:
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />

Usage #

Import package:

import 'package:multi_downloader/multi_downloader.dart';

Initialize

WidgetsFlutterBinding.ensureInitialized();
await FlutterDownloader.initialize();
  • Note: the plugin must be initialized before using.

Create new download task:

final taskId = await FlutterDownloader.enqueue(
  url: 'your download link',
  savedDir: 'the path of directory where you want to save downloaded files',
  showNotification: true, // show download progress in status bar (for Android)
  openFileFromNotification: true, // click on notification to open downloaded file (for Android)
);

Update download progress:

FlutterDownloader.registerCallback(callback); // callback is a top-level or static function

Important note: your UI is rendered in the main isolate, while download events come from a background isolate (in other words, codes in callback are run in the background isolate), so you have to handle the communication between two isolates. For example:

ReceivePort _port = ReceivePort();

@override
void initState() {
	super.initState();

	IsolateNameServer.registerPortWithName(_port.sendPort, 'downloader_send_port');
	_port.listen((dynamic data) {
		String id = data[0];
		DownloadTaskStatus status = data[1];
		int progress = data[2];
		setState((){ });
	});

	FlutterDownloader.registerCallback(downloadCallback);
}

@override
void dispose() {
	IsolateNameServer.removePortNameMapping('downloader_send_port');
	super.dispose();
}

static void downloadCallback(String id, DownloadTaskStatus status, int progress) {
	final SendPort send = IsolateNameServer.lookupPortByName('downloader_send_port');
	send.send([id, status, progress]);
}

Load all tasks:

final tasks = await FlutterDownloader.loadTasks();

Load tasks with conditions:

final tasks = await FlutterDownloader.loadTasksWithRawQuery(query: query);
  • Note: In order to parse data into DownloadTask object successfully, you should load data with all fields from DB (in the other word, use: SELECT * ). For example:
SELECT * FROM task WHERE status=3
  • Note: the following is the schema of task table where this plugin stores tasks information
CREATE TABLE `task` (
	`id`	INTEGER PRIMARY KEY AUTOINCREMENT,
	`task_id`	VARCHAR ( 256 ),
	`url`	TEXT,
	`status`	INTEGER DEFAULT 0,
	`progress`	INTEGER DEFAULT 0,
	`file_name`	TEXT,
	`saved_dir`	TEXT,
	`resumable`	TINYINT DEFAULT 0,
	`headers`	TEXT,
	`show_notification`	TINYINT DEFAULT 0,
	`open_file_from_notification`	TINYINT DEFAULT 0,
	`time_created`	INTEGER DEFAULT 0
);

Cancel a task:

FlutterDownloader.cancel(taskId: taskId);

Cancel all tasks:

FlutterDownloader.cancelAll();

Pause a task:

FlutterDownloader.pause(taskId: taskId);

Resume a task:

FlutterDownloader.resume(taskId: taskId);
  • Note: resume() will return a new taskId corresponding to a new background task that is created to continue the download process. You should replace the original taskId (that is marked as paused status) by this new taskId to continue tracking the download progress.

Retry a failed task:

FlutterDownloader.retry(taskId: taskId);
  • Note: retry() will return a new taskId (like resume())

Remove a task:

FlutterDownloader.remove(taskId: taskId, shouldDeleteContent:false);

Open and preview a downloaded file:

FlutterDownloader.open(taskId: taskId);
  • Note: in Android, you can only open a downloaded file if it is placed in the external storage and there's at least one application that can read that file type on your device.

Bugs/Requests #

If you encounter any problems feel free to open an issue. If you feel the library is missing a feature, please raise a ticket on Github. Pull request are also welcome.

1.0.6 - 25.04.2020 #

  • Bug Fixes On IOS Delete Download

1.0.5 - 25.04.2020 #

  • Bug Fixes On IOS

1.0.4 - 23.04.2020 #

  • Bug Fixes On IOS

1.0.3 - 23.04.2020 #

  • Update the Pods

1.0.2 - 23.04.2020 #

  • Update the Pods

1.0.1 - 21.04.2020 #

  • Translate the Strings

1.0.0 - 05.04.2020 #

  • correct README document

0.0.1 - 05.04.2020 #

  • Initial version, created by Warith

example/lib/main.dart

import 'dart:isolate';
import 'dart:ui';

import 'package:flutter/material.dart';
import 'dart:async';
import 'package:path_provider/path_provider.dart';
import 'package:multi_downloader/multi_downloader.dart';
import 'package:permission_handler/permission_handler.dart';
import 'dart:io';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await MultiDownloader.initialize();

  runApp(new MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final platform = Theme.of(context).platform;

    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new MyHomePage(
        title: 'Downloader',
        platform: platform,
      ),
    );
  }
}

class MyHomePage extends StatefulWidget with WidgetsBindingObserver {
  final TargetPlatform platform;

  MyHomePage({Key key, this.title, this.platform}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final _documents = [
    {
      'name': 'Learning Android Studio',
      'link':
          'http://barbra-coco.dyndns.org/student/learning_android_studio.pdf'
    },
    {
      'name': 'Android Programming Cookbook',
      'link':
          'http://enos.itcollege.ee/~jpoial/allalaadimised/reading/Android-Programming-Cookbook.pdf'
    },
    {
      'name': 'iOS Programming Guide',
      'link':
          'http://darwinlogic.com/uploads/education/iOS_Programming_Guide.pdf'
    },
    {
      'name': 'Objective-C Programming (Pre-Course Workbook',
      'link':
          'https://www.bignerdranch.com/documents/objective-c-prereading-assignment.pdf'
    },
  ];

  final _images = [
    {
      'name': 'Arches National Park',
      'link':
          'https://upload.wikimedia.org/wikipedia/commons/6/60/The_Organ_at_Arches_National_Park_Utah_Corrected.jpg'
    },
    {
      'name': 'Canyonlands National Park',
      'link':
          'https://upload.wikimedia.org/wikipedia/commons/7/78/Canyonlands_National_Park%E2%80%A6Needles_area_%286294480744%29.jpg'
    },
    {
      'name': 'Death Valley National Park',
      'link':
          'https://upload.wikimedia.org/wikipedia/commons/b/b2/Sand_Dunes_in_Death_Valley_National_Park.jpg'
    },
    {
      'name': 'Gates of the Arctic National Park and Preserve',
      'link':
          'https://upload.wikimedia.org/wikipedia/commons/e/e4/GatesofArctic.jpg'
    }
  ];

  final _videos = [
    {
      'name': 'Big Buck Bunny',
      'link':
          'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4'
    },
    {
      'name': 'Elephant Dream',
      'link':
          'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4'
    }
  ];

  List<_TaskInfo> _tasks;
  List<_ItemHolder> _items;
  bool _isLoading;
  bool _permissionReady;
  String _localPath;
  ReceivePort _port = ReceivePort();
  PermissionStatus _permissionStatus = PermissionStatus.undetermined;

  @override
  void initState() {
    super.initState();

    _bindBackgroundIsolate();

    MultiDownloader.registerCallback(downloadCallback);

    _isLoading = true;
    _permissionReady = false;

    _prepare();
  }

  @override
  void dispose() {
    _unbindBackgroundIsolate();
    super.dispose();
  }

  void _bindBackgroundIsolate() {
    bool isSuccess = IsolateNameServer.registerPortWithName(
        _port.sendPort, 'downloader_send_port');
    if (!isSuccess) {
      _unbindBackgroundIsolate();
      _bindBackgroundIsolate();
      return;
    }
    _port.listen((dynamic data) {
      print('UI Isolate Callback: $data');
      String id = data[0];
      DownloadTaskStatus status = data[1];
      int progress = data[2];

      final task = _tasks?.firstWhere((task) => task.taskId == id);
      if (task != null) {
        setState(() {
          task.status = status;
          task.progress = progress;
        });
      }
    });
  }

  void _unbindBackgroundIsolate() {
    IsolateNameServer.removePortNameMapping('downloader_send_port');
  }

  static void downloadCallback(
      String id, DownloadTaskStatus status, int progress) {
    print(
        'Background Isolate Callback: task ($id) is in status ($status) and process ($progress)');
    final SendPort send =
        IsolateNameServer.lookupPortByName('downloader_send_port');
    send.send([id, status, progress]);
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: Builder(
          builder: (context) => _isLoading
              ? new Center(
                  child: new CircularProgressIndicator(),
                )
              : _permissionReady
                  ? new Container(
                      child: new ListView(
                        padding: const EdgeInsets.symmetric(vertical: 16.0),
                        children: _items
                            .map((item) => item.task == null
                                ? new Container(
                                    padding: const EdgeInsets.symmetric(
                                        horizontal: 16.0, vertical: 8.0),
                                    child: Text(
                                      item.name,
                                      style: TextStyle(
                                          fontWeight: FontWeight.bold,
                                          color: Colors.blue,
                                          fontSize: 18.0),
                                    ),
                                  )
                                : new Container(
                                    padding: const EdgeInsets.only(
                                        left: 16.0, right: 8.0),
                                    child: InkWell(
                                      onTap: item.task.status ==
                                              DownloadTaskStatus.complete
                                          ? () {
                                              _openDownloadedFile(item.task)
                                                  .then((success) {
                                                if (!success) {
                                                  Scaffold.of(context)
                                                      .showSnackBar(SnackBar(
                                                          content: Text(
                                                              'Cannot open this file')));
                                                }
                                              });
                                            }
                                          : null,
                                      child: new Stack(
                                        children: <Widget>[
                                          new Container(
                                            width: double.infinity,
                                            height: 64.0,
                                            child: new Row(
                                              crossAxisAlignment:
                                                  CrossAxisAlignment.center,
                                              children: <Widget>[
                                                new Expanded(
                                                  child: new Text(
                                                    item.name,
                                                    maxLines: 1,
                                                    softWrap: true,
                                                    overflow:
                                                        TextOverflow.ellipsis,
                                                  ),
                                                ),
                                                new Padding(
                                                  padding:
                                                      const EdgeInsets.only(
                                                          left: 8.0),
                                                  child: _buildActionForTask(
                                                      item.task),
                                                ),
                                              ],
                                            ),
                                          ),
                                          item.task.status ==
                                                      DownloadTaskStatus
                                                          .running ||
                                                  item.task.status ==
                                                      DownloadTaskStatus.paused
                                              ? new Positioned(
                                                  left: 0.0,
                                                  right: 0.0,
                                                  bottom: 0.0,
                                                  child:
                                                      new LinearProgressIndicator(
                                                    value: item.task.progress /
                                                        100,
                                                  ),
                                                )
                                              : new Container()
                                        ]
                                            .where((child) => child != null)
                                            .toList(),
                                      ),
                                    ),
                                  ))
                            .toList(),
                      ),
                    )
                  : new Container(
                      child: Center(
                        child: Column(
                          mainAxisSize: MainAxisSize.min,
                          crossAxisAlignment: CrossAxisAlignment.center,
                          children: [
                            Padding(
                              padding:
                                  const EdgeInsets.symmetric(horizontal: 24.0),
                              child: Text(
                                'Please grant accessing storage permission to continue -_-',
                                textAlign: TextAlign.center,
                                style: TextStyle(
                                    color: Colors.blueGrey, fontSize: 18.0),
                              ),
                            ),
                            SizedBox(
                              height: 32.0,
                            ),
                            FlatButton(
                                onPressed: () {
                                  _checkPermission().then((hasGranted) {
                                    setState(() {
                                      _permissionReady = hasGranted;
                                    });
                                  });
                                },
                                child: Text(
                                  'Retry',
                                  style: TextStyle(
                                      color: Colors.blue,
                                      fontWeight: FontWeight.bold,
                                      fontSize: 20.0),
                                ))
                          ],
                        ),
                      ),
                    )),
    );
  }

  Widget _buildActionForTask(_TaskInfo task) {
    if (task.status == DownloadTaskStatus.undefined) {
      return new RawMaterialButton(
        onPressed: () {
          _requestDownload(task);
        },
        child: new Icon(Icons.file_download),
        shape: new CircleBorder(),
        constraints: new BoxConstraints(minHeight: 32.0, minWidth: 32.0),
      );
    } else if (task.status == DownloadTaskStatus.running) {
      return new RawMaterialButton(
        onPressed: () {
          _pauseDownload(task);
        },
        child: new Icon(
          Icons.pause,
          color: Colors.red,
        ),
        shape: new CircleBorder(),
        constraints: new BoxConstraints(minHeight: 32.0, minWidth: 32.0),
      );
    } else if (task.status == DownloadTaskStatus.paused) {
      return new RawMaterialButton(
        onPressed: () {
          _resumeDownload(task);
        },
        child: new Icon(
          Icons.play_arrow,
          color: Colors.green,
        ),
        shape: new CircleBorder(),
        constraints: new BoxConstraints(minHeight: 32.0, minWidth: 32.0),
      );
    } else if (task.status == DownloadTaskStatus.complete) {
      return Row(
        mainAxisSize: MainAxisSize.min,
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          new Text(
            'Ready',
            style: new TextStyle(color: Colors.green),
          ),
          RawMaterialButton(
            onPressed: () {
              _delete(task);
            },
            child: Icon(
              Icons.delete_forever,
              color: Colors.red,
            ),
            shape: new CircleBorder(),
            constraints: new BoxConstraints(minHeight: 32.0, minWidth: 32.0),
          )
        ],
      );
    } else if (task.status == DownloadTaskStatus.canceled) {
      return new Text('Canceled', style: new TextStyle(color: Colors.red));
    } else if (task.status == DownloadTaskStatus.failed) {
      return Row(
        mainAxisSize: MainAxisSize.min,
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          new Text('Failed', style: new TextStyle(color: Colors.red)),
          RawMaterialButton(
            onPressed: () {
              _retryDownload(task);
            },
            child: Icon(
              Icons.refresh,
              color: Colors.green,
            ),
            shape: new CircleBorder(),
            constraints: new BoxConstraints(minHeight: 32.0, minWidth: 32.0),
          )
        ],
      );
    } else {
      return null;
    }
  }

  void _requestDownload(_TaskInfo task) async {
    task.taskId = await MultiDownloader.enqueue(
        url: task.link,
        headers: {"auth": "test_for_sql_encoding"},
        savedDir: _localPath,
        showNotification: true,
        openFileFromNotification: true);
  }

  void _cancelDownload(_TaskInfo task) async {
    await MultiDownloader.cancel(taskId: task.taskId);
  }

  void _pauseDownload(_TaskInfo task) async {
    await MultiDownloader.pause(taskId: task.taskId);
  }

  void _resumeDownload(_TaskInfo task) async {
    String newTaskId = await MultiDownloader.resume(taskId: task.taskId);
    task.taskId = newTaskId;
  }

  void _retryDownload(_TaskInfo task) async {
    String newTaskId = await MultiDownloader.retry(taskId: task.taskId);
    task.taskId = newTaskId;
  }

  Future<bool> _openDownloadedFile(_TaskInfo task) {
    return MultiDownloader.open(taskId: task.taskId);
  }

  void _delete(_TaskInfo task) async {
    await MultiDownloader.remove(
        taskId: task.taskId, shouldDeleteContent: true);
    await _prepare();
    setState(() {});
  }


  Future<void> requestPermission(Permission permission) async {
    final status = await permission.request();
    setState(() {
      print(status);
      _permissionStatus = status;
      print(_permissionStatus);
    });
  }


  Future<bool> _checkPermission() async {
    if (widget.platform == TargetPlatform.android) {

      if (_permissionStatus != PermissionStatus.granted) {

        switch (_permissionStatus) {
          case PermissionStatus.denied:
            requestPermission(Permission.storage);
            return true;
          case PermissionStatus.permanentlyDenied:
            requestPermission(Permission.storage);
            return true;
          case PermissionStatus.granted:
            return true;
          default:
            requestPermission(Permission.storage);
            return true;
            }
            
        } else {
          return true;
        }
        
      } else {
      return true;
    }
  }

  Future<Null> _prepare() async {
    final tasks = await MultiDownloader.loadTasks();

    int count = 0;
    _tasks = [];
    _items = [];

    _tasks.addAll(_documents.map((document) =>
        _TaskInfo(name: document['name'], link: document['link'])));

    _items.add(_ItemHolder(name: 'Documents'));
    for (int i = count; i < _tasks.length; i++) {
      _items.add(_ItemHolder(name: _tasks[i].name, task: _tasks[i]));
      count++;
    }

    _tasks.addAll(_images
        .map((image) => _TaskInfo(name: image['name'], link: image['link'])));

    _items.add(_ItemHolder(name: 'Images'));
    for (int i = count; i < _tasks.length; i++) {
      _items.add(_ItemHolder(name: _tasks[i].name, task: _tasks[i]));
      count++;
    }

    _tasks.addAll(_videos
        .map((video) => _TaskInfo(name: video['name'], link: video['link'])));

    _items.add(_ItemHolder(name: 'Videos'));
    for (int i = count; i < _tasks.length; i++) {
      _items.add(_ItemHolder(name: _tasks[i].name, task: _tasks[i]));
      count++;
    }

    tasks?.forEach((task) {
      for (_TaskInfo info in _tasks) {
        if (info.link == task.url) {
          info.taskId = task.taskId;
          info.status = task.status;
          info.progress = task.progress;
        }
      }
    });

    _permissionReady = await _checkPermission();

    _localPath = (await _findLocalPath()) + Platform.pathSeparator + 'Download';

    final savedDir = Directory(_localPath);
    bool hasExisted = await savedDir.exists();
    if (!hasExisted) {
      savedDir.create();
    }

    setState(() {
      _isLoading = false;
    });
  }

  Future<String> _findLocalPath() async {
    final directory = widget.platform == TargetPlatform.android
        ? await getExternalStorageDirectory()
        : await getApplicationDocumentsDirectory();
    return directory.path;
  }
}

class _TaskInfo {
  final String name;
  final String link;

  String taskId;
  int progress = 0;
  DownloadTaskStatus status = DownloadTaskStatus.undefined;

  _TaskInfo({this.name, this.link});
}

class _ItemHolder {
  final String name;
  final _TaskInfo task;

  _ItemHolder({this.name, this.task});
}

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  multi_downloader: ^1.0.6

2. Install it

You can install packages from the command line:

with Flutter:


$ flutter pub get

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:multi_downloader/multi_downloader.dart';
  
Popularity:
Describes how popular the package is relative to other packages. [more]
42
Health:
Code health derived from static analysis. [more]
100
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
100
Overall:
Weighted score of the above. [more]
71
Learn more about scoring.

We analyzed this package on Jul 11, 2020, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.8.4
  • pana: 0.13.15
  • Flutter: 1.17.5

Analysis suggestions

Package does not support Flutter platform Linux

Because:

  • package:multi_downloader/multi_downloader.dart that declares support for platforms: Android, iOS

Package does not support Flutter platform Web

Because:

  • package:multi_downloader/multi_downloader.dart that declares support for platforms: Android, iOS

Package does not support Flutter platform Windows

Because:

  • package:multi_downloader/multi_downloader.dart that declares support for platforms: Android, iOS

Package does not support Flutter platform macOS

Because:

  • package:multi_downloader/multi_downloader.dart that declares support for platforms: Android, iOS

Package not compatible with SDK dart

Because:

  • multi_downloader that is a package requiring null.

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=1.20.1 <3.0.0
flutter 0.0.0
path_provider ^1.6.5 1.6.11
permission_handler ^5.0.0+hotfix.3 5.0.1+1
Transitive dependencies
collection 1.14.12 1.14.13
file 5.2.1
intl 0.16.1
meta 1.1.8 1.2.2
path 1.7.0
path_provider_linux 0.0.1+2
path_provider_macos 0.0.4+3
path_provider_platform_interface 1.0.2
permission_handler_platform_interface 2.0.1
platform 2.2.1
plugin_platform_interface 1.0.2
process 3.0.13
sky_engine 0.0.99
typed_data 1.1.6 1.2.0
vector_math 2.0.8 2.1.0-nullsafety
xdg_directories 0.1.0
Dev dependencies
flutter_test