flutter_downloader 1.0.5

  • README.md
  • CHANGELOG.md
  • Example
  • Installing
  • Versions
  • 94

Flutter Downloader

pub package

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

  • Open ios project (file Runner.xcworkspace) in Xcode.

  • Enable background mode.

  • Add sqlite library.

Note: If you want to download file with HTTP request, you need to disable Apple Transport Security (ATS) feature.

  • Disable ATS for a specific domain only: (add following codes to the end of 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>
  • Completely disable ATS: (add following codes to the end of your Info.plist file)
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key><true/>
</dict>

Android integration

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 (inside application tag):
<provider
    android:name="vn.hunghd.flutterdownloader.DownloadedFileProvider"
    android:authorities="${applicationId}.flutter_downloader.provider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/provider_paths"/>
</provider>
  • you have to save your downloaded files in external storage (where the other applications have permission to read your files)

Note: 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)

Usage

import 'package:flutter_downloader/flutter_downloader.dart';

Initialize plugin:

FlutterDownloader.initialize(
  maxConcurrentTasks: 3, // config the maximum number of tasks running at a moment
  messages: {....} // localize messages for Android notification
);

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((id, status, progress) {
  // code to update your UI
});

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())

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.5 - 22.10.2018

  • Android: re-config dependencies

1.0.4 - 20.10.2018

  • Android: upgrade WorkManager to v1.0.0-alpha10

1.0.3 - 29.09.2018

  • Android: upgrade compile sdk version to 28

1.0.2 - 20.09.2018

  • Fixed Flutter Community badge.

1.0.1 - 20.09.2018

1.0.0 - 09.09.2018

  • NEW features: initialize, loadTasksWithRawQuery, pause, resume, retry, open
  • IMPORTANT: the plugin must be initialized by initialize() at first
  • BREAKING CHANGE: clickToOpenDownloadedFile now renames to openFileFromNotification (to prevent confusing from open feature). Static property maximumConcurrentTask has been removed, this configuration now moves into initialize() method.
  • full support SQLite on both Android and iOS side, the plugin now itself manages its states persistently and exposes loadTasksWithRawQuery api that helps developers to load tasks from SQLite database with customized conditions
  • support localizing Android notification messages with messages parameter of initialize() method
  • full support opening and previewing downloaded file with open() method
  • (iOS integration) no need to override application:handleEventsForBackgroundURLSession:completionHandler: manually anymore, the plugin now itself takes responsibility for handling it

0.1.1 - 29.08.2018

  • fix bugs: SQLite leak
  • new feature: support configuration of the maximum of concurrent download tasks
  • upgrade WorkManager to v1.0.0-alpha08

0.1.0 - 12.08.2018

  • add: handle click on notification to open downloaded file (for Android)

0.0.9 - 10.08.2018

  • re-config to support Dart2

0.0.8 - 10.08.2018

  • upgrade WorkManager to v1.0.0-alpha06
  • fix bug: disable sound on notifications

0.0.7 - 28.06.2018

  • upgrade WorkManager to v1.0.0-alpha04

0.0.6 - 28.06.2018

  • upgrade WorkManager to v1.0.0-alpha03
  • change default value of showNotification to true (it makes sense on Android 8.0 and above, it helps our tasks not to be killed by system when the app goes to background)

0.0.5 - 22.06.2018

  • update metadata

0.0.4 - 15.06.2018

  • fix bug: Worker finished with FAILURE on Android API 26 and above

0.0.3 - 11.06.2018

  • support HTTP headers

0.0.2 - 08.06.2018

  • correct README document

0.0.1 - 07.06.2018

  • initial release.

example/lib/main.dart

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

void main() {
  FlutterDownloader.initialize(maxConcurrentTasks: 3);
  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 {
  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> with WidgetsBindingObserver {
  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':
          'https://www.sample-videos.com/video/mp4/720/big_buck_bunny_720p_30mb.mp4'
    },
    {
      'name': 'Star Trails',
      'link':
          'http://mirrors.standaloneinstaller.com/video-sample/star_trails.mp4'
    }
  ];

  List<_TaskInfo> _tasks;
  List<_ItemHolder> _items;
  bool _isLoading;
  bool _permissisonReady;
  String _localPath;

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

    FlutterDownloader.registerCallback((id, status, progress) {
      print(
          'Download task ($id) is in status ($status) and process ($progress)');
      final task = _tasks.firstWhere((task) => task.taskId == id);
      setState(() {
        task?.status = status;
        task?.progress = progress;
      });
    });

    _isLoading = true;
    _permissisonReady = false;

    _prepare();
  }

  @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(),
                )
              : _permissisonReady
                  ? 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(() {
                                      _permissisonReady = 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 new Text(
        'Ready',
        style: new TextStyle(color: Colors.green),
      );
    } 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 FlutterDownloader.enqueue(
        url: task.link,
        savedDir: _localPath,
        showNotification: true,
        openFileFromNotification: false);
  }

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

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

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

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

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

  Future<bool> _checkPermission() async {
    if (widget.platform == TargetPlatform.android) {
      PermissionStatus permission = await PermissionHandler()
          .checkPermissionStatus(PermissionGroup.storage);
      if (permission != PermissionStatus.granted) {
        Map<PermissionGroup, PermissionStatus> permissions =
            await PermissionHandler()
                .requestPermissions([PermissionGroup.storage]);
        if (permissions[PermissionGroup.storage] == PermissionStatus.granted) {
          return true;
        }
      } else {
        return true;
      }
    } else {
      return true;
    }
    return false;
  }

  Future<Null> _prepare() async {
    final tasks = await FlutterDownloader.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;
        }
      }
    });

    _permissisonReady = await _checkPermission();

    _localPath = (await _findLocalPath()) + '/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:
  flutter_downloader: ^1.0.5

2. Install it

You can install packages from the command line:

with Flutter:


$ flutter packages get

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

3. Import it

Now in your Dart code, you can use:


import 'package:flutter_downloader/flutter_downloader.dart';
  
Version Uploaded Documentation Archive
1.0.5 Oct 22, 2018 Go to the documentation of flutter_downloader 1.0.5 Download flutter_downloader 1.0.5 archive
1.0.4 Oct 20, 2018 Go to the documentation of flutter_downloader 1.0.4 Download flutter_downloader 1.0.4 archive
1.0.3 Sep 29, 2018 Go to the documentation of flutter_downloader 1.0.3 Download flutter_downloader 1.0.3 archive
1.0.2 Sep 20, 2018 Go to the documentation of flutter_downloader 1.0.2 Download flutter_downloader 1.0.2 archive
1.0.1 Sep 20, 2018 Go to the documentation of flutter_downloader 1.0.1 Download flutter_downloader 1.0.1 archive
1.0.0 Sep 20, 2018 Go to the documentation of flutter_downloader 1.0.0 Download flutter_downloader 1.0.0 archive
0.1.1 Aug 29, 2018 Go to the documentation of flutter_downloader 0.1.1 Download flutter_downloader 0.1.1 archive
0.1.0 Aug 12, 2018 Go to the documentation of flutter_downloader 0.1.0 Download flutter_downloader 0.1.0 archive
0.0.9 Aug 10, 2018 Go to the documentation of flutter_downloader 0.0.9 Download flutter_downloader 0.0.9 archive
0.0.8 Aug 10, 2018 Go to the documentation of flutter_downloader 0.0.8 Download flutter_downloader 0.0.8 archive

All 17 versions...

Popularity:
Describes how popular the package is relative to other packages. [more]
88
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]
94
Learn more about scoring.

We analyzed this package on Oct 22, 2018, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.0.0
  • pana: 0.12.4
  • Flutter: 0.9.5

Platforms

Detected platforms: Flutter

References Flutter, and has no conflicting libraries.

Health suggestions

Format lib/flutter_downloader.dart.

Run flutter format to format lib/flutter_downloader.dart.

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=1.20.1 <3.0.0
flutter 0.0.0
Transitive dependencies
collection 1.14.11
meta 1.1.6
sky_engine 0.0.99
typed_data 1.1.6
vector_math 2.0.8
Dev dependencies
flutter_test