android – Flutter: Downloading failed always using flutter_downlaoder


I am using flutter_downloader to download the file from a url and i tested that url is working fine and file is downloading if i load that url on browser.

The same code is working perfectly fine on iOS

I am facing issue in android only, file starts downloading and instantly downloading got failed. I tried to reach on github with the same error but haven’t get the response. some other devs are also getting this error please check here

I am getting the following errors:

W/System.err(10971): java.lang.NullPointerException: httpConn.contentType must not be null
W/System.err(10971):    at vn.hunghd.flutterdownloader.DownloadWorker.downloadFile(DownloadWorker.kt:342)
W/System.err(10971):    at vn.hunghd.flutterdownloader.DownloadWorker.doWork(DownloadWorker.kt:208)
W/System.err(10971):    at androidx.work.Worker$1.run(Worker.java:86)
W/System.err(10971):    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
W/System.err(10971):    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
W/System.err(10971):    at java.lang.Thread.run(Thread.java:923)

On initialization i am getting the following error:

E/flutter (11803): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: 'package:flutter_downloader/src/downloader.dart': Failed assertion: line 49 pos 7: '!_initialized': plugin flutter_downloader has already been initialized
E/flutter (11803): #0      _AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:51:61)
E/flutter (11803): #1      _AssertionError._throwNew (dart:core-patch/errors_patch.dart:40:5)

here is my manifest with permissions:

<application
       android:debuggable="true"
       android:largeHeap="true"
       tools:replace="android:label"
       android:requestLegacyExternalStorage="true"
       android:label="GWEP Navigator"
       android:name="${applicationName}"
       android:usesCleartextTraffic="true"
       android:icon="@mipmap/ic_launcher">

       <provider
           android:name="vn.hunghd.flutterdownloader.DownloadedFileProvider"
           android:authorities="im.mingguang.mingguang_app.flutter_downloader.provider"
           android:exported="false"
           android:grantUriPermissions="true"
           android:requestLegacyExternalStorage="true">
           <meta-data
               android:name="android.support.FILE_PROVIDER_PATHS"
               android:resource="@xml/provider_paths"/>
       </provider>
 <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION" />

Following is the downloading class code.

import 'dart:isolate';
import 'dart:ui';
import 'dart:async';
import 'dart:io';

import 'package:android_path_provider/android_path_provider.dart';
import 'package:device_info/device_info.dart';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:permission_handler/permission_handler.dart';


const debug = true;

List<TaskInfo>? taskInfoList = [];
late List<ItemHolder> itemholderList = [];

class DownloadFileScreen extends StatefulWidget with WidgetsBindingObserver {
  static const String routeName = "/DownloadFileScreen";

  TargetPlatform? platform;

  DownloadFileScreen({Key? key}) : super(key: key);

  // final String? title;

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

class _DownloadFileScreenState extends State<DownloadFileScreen> {
  late bool _isLoading;
  late bool _permissionReady;
  late String _localPath;
  ReceivePort _port = ReceivePort();

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

    FlutterDownloader.registerCallback(downloadCallback);

    _isLoading = true;
    _permissionReady = false;

    _prepare();
  }

  @override
  void dispose() {
    _unbindBackgroundIsolate();
    super.dispose();
  }
  void _bindBackgroundIsolate() async {
    await FlutterDownloader.initialize(
        debug: true, // optional: set to false to disable printing logs to console (default: true)
        ignoreSsl: true // option: set to false to disable working with http links (default: false)
    );
    bool isSuccess = IsolateNameServer.registerPortWithName(_port.sendPort, 'downloader_send_port');
    if (!isSuccess) {
      _unbindBackgroundIsolate();
      _bindBackgroundIsolate();
      return;
    }
    _port.listen((dynamic data) async {
      if (debug) {
        print('UI Isolate Callback: $data');
      }
      String? id = data[0];
      DownloadTaskStatus? status = data[1] == 2
          ? DownloadTaskStatus.running
          : data[1] == 4
          ? DownloadTaskStatus.failed
          : data[1] == 3
          ? DownloadTaskStatus.complete
          : DownloadTaskStatus.undefined;
      int? progress = data[2];

      if (taskInfoList != null && taskInfoList!.isNotEmpty) {
        final task = taskInfoList!.firstWhere((task) => task.taskId == id);
        if (status == DownloadTaskStatus.complete) {
          await updateAoiRequest(taskInfoList, id);
        }
        setState(() {
          task.status = status;
          task.progress = progress;
        });
      }
    });
    FlutterDownloader.registerCallback(downloadCallback);
  }

  Future<void> updateAoiRequest(List<TaskInfo>? taskInfoList, String? id) async {
    // final task = taskInfoList!.firstWhere((task) => task.taskId == id);
    // AoiRequest? aoiRequest = await AoiDBHelper.getAoiRequestByDownloadUrl(task.link!);
    // if (aoiRequest != null) {
    //   aoiRequest.downloadStatus = AoiDownloadStatus.Complete;
    //   AoiRequestBloc.databaseBloc.updateAoiRequest(aoiRequest);
    // }

    // print(aoiRequest?.fileName);
  }

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

  @pragma('vm:entry-point')
  // static void downloadCallback(String id, DownloadTaskStatus status, int progress) {
  static void downloadCallback(String id, status, int progress) {
    if (debug) {
      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) {
    widget.platform = Theme.of(context).platform;
    return Scaffold(
      appBar: AppBar(
        title: Text("Download list"),
      ),
      body: Builder(
          builder: (context) => _isLoading
              ? Center(
            child: CircularProgressIndicator(),
          )
              : _permissionReady
              ? _buildDownloadList()
              : _buildNoPermissionWarning()),
    );
  }

  Widget _buildDownloadList() => Container(
    child: ListView(
      padding: const EdgeInsets.symmetric(vertical: 16.0),
      children: itemholderList
          .map((item) => item.task == null
          ? _buildListSection(item.name ?? 'Files')
          : DownloadItem(
        data: item,
        onItemClick: (task) {
          _openDownloadedFile(task).then((success) {
            if (!success) {
              ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Cannot open this file')));
            }
          });
        },
        onActionClick: (task) {
          if (task.status == DownloadTaskStatus.undefined) {
            _requestDownload(task);
          } else if (task.status == DownloadTaskStatus.running) {
            _pauseDownload(task);
          } else if (task.status == DownloadTaskStatus.paused) {
            _resumeDownload(task);
          } else if (task.status == DownloadTaskStatus.complete) {
            _delete(task);
          } else if (task.status == DownloadTaskStatus.failed) {
            _retryDownload(task);
          }
        },
      ))
          .toList(),
    ),
  );

  Widget _buildListSection(String title) => Container(
    padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
    child: Text(
      title,
      style: TextStyle(fontWeight: FontWeight.bold, color: Colors.blue, fontSize: 18.0),
    ),
  );

  Widget _buildNoPermissionWarning() => 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,
          ),
          TextButton(
              onPressed: () {
                _retryRequestPermission();
              },
              child: Text(
                'Retry',
                style: TextStyle(color: Colors.blue, fontWeight: FontWeight.bold, fontSize: 20.0),
              ))
        ],
      ),
    ),
  );

  Future<void> _retryRequestPermission() async {
    final hasGranted = await _checkPermission();

    if (hasGranted) {
      await _prepareSaveDir();
    }

    setState(() {
      _permissionReady = hasGranted;
    });
  }

  void _requestDownload(TaskInfo task) async {
    print(task.localDownloadPath);
    task.taskId = await FlutterDownloader.enqueue(
      url: task.link!,
      headers: {"content-type": "application/json"},
      // headers: {"content-type": "multipart/form-data"},
      fileName: task.fileName,
      savedDir: task.localDownloadPath!,
      showNotification: true,
      openFileFromNotification: true,
      // saveInPublicStorage: true,
    );
  }

  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) {
    if (task != null) {
      return FlutterDownloader.open(taskId: task.taskId!);
    } else {
      return Future.value(false);
    }
  }

  void _delete(TaskInfo task) async {
    await FlutterDownloader.remove(taskId: task.taskId!, shouldDeleteContent: true);
    await _prepare();
    setState(() {});
  }

  Future<bool> _checkPermission() async {
    print('check permission called');
    if (Platform.isIOS) return true;

    DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
    AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
    if (widget.platform == TargetPlatform.android && androidInfo.version.sdkInt <= 28) {
      final status = await Permission.storage.status;
      if (status != PermissionStatus.granted) {
        final result = await Permission.storage.request();
        if (result == PermissionStatus.granted) {
          return true;
        }
      } else {
        return true;
      }
    } else {
      return true;
    }
    return false;
  }

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

    // int count = 0;
    // taskInfoList = [];
    // itemholderList = [];

    // taskInfoList!.addAll(_documents.map((document) =>
    //     TaskInfo(name: document['name'], link: document['link'])));
    //
    // itemholderList.add(ItemHolder(name: 'Documents'));
    // for (int i = count; i < taskInfoList!.length; i++) {
    //   itemholderList.add(ItemHolder(name: taskInfoList![i].name, task: taskInfoList![i]));
    //   count++;
    // }
    //
    // taskInfoList!.addAll(_images
    //     .map((image) => TaskInfo(name: image['name'], link: image['link'])));
    //
    // itemholderList.add(ItemHolder(name: 'Images'));
    // for (int i = count; i < taskInfoList!.length; i++) {
    //   itemholderList.add(ItemHolder(name: taskInfoList![i].name, task: taskInfoList![i]));
    //   count++;
    // }
    //
    // taskInfoList!.addAll(_videos
    //     .map((video) => TaskInfo(name: video['name'], link: video['link'])));
    //
    // itemholderList.add(ItemHolder(name: 'Videos'));
    // for (int i = count; i < taskInfoList!.length; i++) {
    //   itemholderList.add(ItemHolder(name: taskInfoList![i].name, task: taskInfoList![i]));
    //   count++;
    // }

    tasks!.forEach((task) async {
      for (TaskInfo info in taskInfoList!) {
        if (info.link == task.url) {
          info.taskId = task.taskId;
          info.status = task.status;
          info.progress = task.progress;
          if (task.status == DownloadTaskStatus.complete) {
            await updateAoiRequest(taskInfoList, task.taskId);
          } else if (task.status == DownloadTaskStatus.undefined) {
            _requestDownload(info);
          }
        }
      }
    });

    for (TaskInfo info in taskInfoList!) {
      if (info.status == DownloadTaskStatus.undefined) {
        _requestDownload(info);
      }
    }

    _permissionReady = await _checkPermission();

    if (_permissionReady) {
      await _prepareSaveDir();
    }

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

  Future<void> _prepareSaveDir() async {
    _localPath = (await _findLocalPath())!;
    final savedDir = Directory(_localPath);
    bool hasExisted = await savedDir.exists();
    if (!hasExisted) {
      savedDir.create();
    }
  }

  Future<String?> _findLocalPath() async {
    var externalStorageDirPath;
    if (Platform.isAndroid) {
      try {
        externalStorageDirPath = await AndroidPathProvider.downloadsPath;
      } catch (e) {
        final directory = await getExternalStorageDirectory();
        externalStorageDirPath = directory?.path;
      }
    } else if (Platform.isIOS) {
      externalStorageDirPath = (await getApplicationDocumentsDirectory()).absolute.path;
    }
    return externalStorageDirPath;
  }
}

class DownloadItem extends StatelessWidget {
  final ItemHolder? data;
  final Function(TaskInfo?)? onItemClick;
  final Function(TaskInfo)? onActionClick;

  DownloadItem({this.data, this.onItemClick, this.onActionClick});

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.only(left: 16.0, right: 8.0),
      child: InkWell(
        onTap: data!.task!.status == DownloadTaskStatus.complete
            ? () {
          onItemClick!(data!.task);
        }
            : null,
        child: Stack(
          children: <Widget>[
            Container(
              width: double.infinity,
              height: 64.0,
              child: Row(
                crossAxisAlignment: CrossAxisAlignment.center,
                children: <Widget>[
                  Expanded(
                    child: Text(
                      data!.name!,
                      maxLines: 1,
                      softWrap: true,
                      overflow: TextOverflow.ellipsis,
                    ),
                  ),
                  Padding(
                    padding: const EdgeInsets.only(left: 8.0),
                    child: _buildActionForTask(data!.task!),
                  ),
                ],
              ),
            ),
            data!.task!.status == DownloadTaskStatus.running || data!.task!.status == DownloadTaskStatus.paused
                ? Positioned(
              left: 0.0,
              right: 0.0,
              bottom: 0.0,
              child: LinearProgressIndicator(
                value: data!.task!.progress! / 100,
              ),
            )
                : Container()
          ].toList(),
        ),
      ),
    );
  }

  Widget? _buildActionForTask(TaskInfo task) {
    if (task.status == DownloadTaskStatus.undefined) {
      return RawMaterialButton(
        onPressed: () {
          onActionClick!(task);
        },
        child: Icon(Icons.file_download),
        shape: CircleBorder(),
        constraints: BoxConstraints(minHeight: 32.0, minWidth: 32.0),
      );
    } else if (task.status == DownloadTaskStatus.running) {
      return RawMaterialButton(
        onPressed: () {
          onActionClick!(task);
        },
        child: Icon(
          Icons.pause,
          color: Colors.red,
        ),
        shape: CircleBorder(),
        constraints: BoxConstraints(minHeight: 32.0, minWidth: 32.0),
      );
    } else if (task.status == DownloadTaskStatus.paused) {
      return RawMaterialButton(
        onPressed: () {
          onActionClick!(task);
        },
        child: Icon(
          Icons.play_arrow,
          color: Colors.green,
        ),
        shape: CircleBorder(),
        constraints: BoxConstraints(minHeight: 32.0, minWidth: 32.0),
      );
    } else if (task.status == DownloadTaskStatus.complete) {
      return Row(
        mainAxisSize: MainAxisSize.min,
        mainAxisAlignment: MainAxisAlignment.end,
        children: const [
          Text(
            'Completed',
            style: TextStyle(color: Colors.green),
          ),
          // RawMaterialButton(
          //   onPressed: () {
          //     onActionClick!(task);
          //   },
          //   child: Icon(
          //     Icons.delete_forever,
          //     color: Colors.red,
          //   ),
          //   shape: CircleBorder(),
          //   constraints: BoxConstraints(minHeight: 32.0, minWidth: 32.0),
          // )
        ],
      );
    } else if (task.status == DownloadTaskStatus.canceled) {
      return Text('Canceled', style: TextStyle(color: Colors.red));
    } else if (task.status == DownloadTaskStatus.failed) {
      return Row(
        mainAxisSize: MainAxisSize.min,
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          Text('Failed', style: TextStyle(color: Colors.red)),
          RawMaterialButton(
            onPressed: () {
              onActionClick!(task);
            },
            child: Icon(
              Icons.refresh,
              color: Colors.green,
            ),
            shape: CircleBorder(),
            constraints: BoxConstraints(minHeight: 32.0, minWidth: 32.0),
          )
        ],
      );
    } else if (task.status == DownloadTaskStatus.enqueued) {
      return Text('Pending', style: TextStyle(color: Colors.orange));
    } else {
      return null;
    }
  }
}

class TaskInfo {
  final String? name;
  final String? link;

  String? taskId;
  int? progress = 0;
  String? localDownloadPath;
  String? fileName;
  DownloadTaskStatus? status = DownloadTaskStatus.undefined;

  TaskInfo({this.name, this.link, this.localDownloadPath, this.fileName});
}

class ItemHolder {
  final String? name;
  final TaskInfo? task;

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

Thanks in advance.

Latest articles

spot_imgspot_img

Related articles

Leave a reply

Please enter your comment!
Please enter your name here

spot_imgspot_img