0

I am building an app where users can download videos, i used provider for state mangagement and the flutter_downloader package to handle downloads, in the debug version of the app (The version where i am building the app) notifyListener is working fine, and i am able to show download progress correctly in the app, but in the release version it does not work, it shows the local notification but does not rebuild my app to show the download progress properly.

  final String? name;
  final String? downloadLink;
  String? taskID;
  String? movieID;
  int? progress = 0;
  DownloadTaskStatus? status = DownloadTaskStatus.undefined;

  TaskInfo({this.name, this.downloadLink, this.movieID});
}

class DownloadQueue extends ChangeNotifier {
  // final List<String> _queue = [];
  List<TaskInfo>? _tasks;
  final ReceivePort _receivePort = ReceivePort();

  Future<String> _downloadUrl(String movieID) async {
    final storageRef = FirebaseStorage.instance.ref();
    final childRef = storageRef.child(movieID);
    final files = await childRef.listAll();
    final movieFilePath = storageRef.child(files.items[0].fullPath);
    final downloadUrl = await movieFilePath.getDownloadURL();
    return downloadUrl;
  }

  void _bindBackgroundIsolate() {
    final isSuccess = IsolateNameServer.registerPortWithName(
        _receivePort.sendPort, 'downloader_send_port');

    if (!isSuccess) {
      _unbindBackgroundIsolate();
      _bindBackgroundIsolate();
      return;
    }

    _receivePort.listen((dynamic message) {
      final taskId = (message as List<dynamic>)[0] as String;
      final status = message[1] as DownloadTaskStatus;
      final progress = message[2] as int;

      if (_tasks != null && _tasks!.isNotEmpty) {
        final task = _tasks!.firstWhere((task) => task.taskID == taskId);
        task.status = status;
        task.progress = progress;
        notifyListeners(); //This is to force the UI to rebuild.
      }
      // notifyListeners();
    });
  }

  void _unbindBackgroundIsolate() {
    // print ("unbinding isolae");
    IsolateNameServer.removePortNameMapping('downloader_send_port');
  }

  static void _downloadCallback(
      String id, DownloadTaskStatus downloadTaskStatus, int progress) {
    IsolateNameServer.lookupPortByName('downloader_send_port')!
        .send([id, downloadTaskStatus, progress]);
    // notifyListeners();
  }

  Future<void> initializeQueue() async {
    // _unbindBackgroundIsolate();
    // _receivePort.close();
    _bindBackgroundIsolate();

    FlutterDownloader.registerCallback(_downloadCallback);

    final tasks = await FlutterDownloader.loadTasks();
    final data = await SharedPref.fetchInfo(Strings.downloadQueue);
    _tasks = [];

    if (!(data == null)) {
      for (var i in data) {
        //populate the tasks list with downloads in queue
        _tasks!.add(
            TaskInfo(name: i, movieID: i, downloadLink: await _downloadUrl(i)));
      }
    }

    for (final task in tasks!) {
      for (final info in _tasks!) {
        if (info.downloadLink == task.url) {
          info.taskID = task.taskId;
          info.status = task.status;
          info.progress = task.progress;
        }
      }
    }
  }

  void startDownload(TaskInfo taskInfo) async {
    final storageRef = FirebaseStorage.instance.ref();
    final childRef = storageRef.child(taskInfo.movieID!);
    final files = await childRef.listAll();
    final movieFilePath = storageRef.child(files.items[0].fullPath);
    final downloadUrl = await movieFilePath.getDownloadURL();

    taskInfo.taskID = await FlutterDownloader.enqueue(
        saveInPublicStorage: true,
        url: downloadUrl,
        openFileFromNotification: true,
        savedDir: await AndroidPathProvider
            .downloadsPath); //"/storage/emulated/0/Downloads/"
    // notifyListeners();
  }

  void pauseDownload(TaskInfo taskInfo) async {
    FlutterDownloader.pause(taskId: taskInfo.taskID!);
    // notifyListeners();
  }

  void cancelDownload(TaskInfo taskInfo) async {
    FlutterDownloader.cancel(taskId: taskInfo.taskID!);
    // notifyListeners();
  }

  void resumeDownload(TaskInfo taskInfo) async {
    final newTaskID = await FlutterDownloader.resume(taskId: taskInfo.taskID!);
    taskInfo.taskID = newTaskID;
    // notifyListeners();
  }

  void retryDownload(TaskInfo taskInfo) async {
    final newTaskID = await FlutterDownloader.retry(taskId: taskInfo.taskID!);
    taskInfo.taskID = newTaskID;
    // notifyListeners();
  }

  void openDownloadedFile(TaskInfo? taskInfo) async {
    // if (taskInfo != null) {
    await FlutterDownloader.open(taskId: taskInfo!.taskID!);
    // } else {
    //   return Future.value(false);
    // }
    // notifyListeners();
  }

  void delete(TaskInfo? taskInfo) async {
    await FlutterDownloader.remove(
        taskId: taskInfo!.taskID!, shouldDeleteContent: true);
    SharedPref.removeInfo(taskInfo.movieID!, Strings.downloadQueue);
    removeFromList(taskInfo.movieID!);
    // notifyListeners();
  }

  Future<void> addToQueue(String id) async {
    SharedPref.saveInfo(id, Strings.downloadQueue);
    _tasks!.add(
        TaskInfo(name: id, movieID: id, downloadLink: await _downloadUrl(id)));
    notifyListeners();
  }

  Future<void> removeFromList(String id, {TaskInfo? taskInfo}) async {
    SharedPref.removeInfo(id, Strings.downloadQueue);
    _tasks!.removeWhere(((element) => element.movieID == id));
    notifyListeners();
  }

  bool checkExists(String id) {
    for (var task in _tasks!) {
      if (task.movieID == id) {
        return true;
      }
    }
    return false;
  }

  List<TaskInfo>? taskList() {
    return _tasks;
  }
} 

Then we have the download screen, i wrapped this page in a consumer so the page can rebuild when there is a change in any of the list of _tasks changes

      backgroundColor: Colors.black,
      body: Consumer<DownloadQueue>(builder: (_, provider, __) {
        return Column(
          children: [
            const DisplayBannerAdWidget(),
            const SizedBox(
              height: 50,
            ),
            Expanded(
              child: ListView.builder(
                itemBuilder: ((context, index) {
                  final task = provider.taskList()![index];
                  return DownloadListItem(
                    data: task,
                    onTap: (task) async {
                      provider.openDownloadedFile(task);
                    },
                    onActionTap: (task) async {
                      if (task!.status == DownloadTaskStatus.undefined) {
                        provider.startDownload(task);
                      } else if (task.status == DownloadTaskStatus.running) {
                        provider.pauseDownload(task);
                      } else if (task.status == DownloadTaskStatus.paused) {
                        provider.resumeDownload(task);
                      } else if (task.status == DownloadTaskStatus.complete ||
                          task.status == DownloadTaskStatus.canceled) {
                        provider.delete(task);
                      } else if (task.status == DownloadTaskStatus.failed) {
                        provider.retryDownload(task);
                      }
                    },
                    cancel: (task) async {
                      provider.delete(task);
                    },
                  );
                }),
                itemCount: provider.taskList()!.length,
              ),
            ),
          ],
        );
      }),
    );

Finally this is the widget to that create the suitable trailing icon and progress indicator to be displayed

class DownloadListItem extends StatelessWidget {
  const DownloadListItem({
    Key? key,
    this.data,
    this.onTap,
    this.onActionTap,
    this.cancel,
  }) : super(key: key);

  final TaskInfo? data;
  final Function(TaskInfo?)? onTap;
  final Function(TaskInfo?)? onActionTap;
  final Function(TaskInfo?)? cancel;

  Widget? _buildTrailing(TaskInfo taskInfo) {
    if (taskInfo.status == DownloadTaskStatus.undefined) {
      return IconButton(
        onPressed: () => onActionTap!(taskInfo),
        constraints: const BoxConstraints(minHeight: 32, minWidth: 32),
        icon: const Icon(Icons.file_download),
      );
    } else if (taskInfo.status == DownloadTaskStatus.running) {
      return Row(
        children: [
          Text('${taskInfo.progress}%'),
          IconButton(
            onPressed: () => onActionTap!(taskInfo),
            constraints: const BoxConstraints(minHeight: 32, minWidth: 32),
            icon: const Icon(
              Icons.pause,
              color: Colors.yellow,
            ),
          )
        ],
      );
    } else if (taskInfo.status == DownloadTaskStatus.paused) {
      return Row(
        children: [
          Text('${taskInfo.progress}%'),
          IconButton(
            onPressed: () => onActionTap!(taskInfo),
            constraints: const BoxConstraints(minHeight: 32, minWidth: 32),
            icon: const Icon(
              Icons.play_arrow,
              color: Colors.green,
            ),
          ),
          IconButton(
              onPressed: () => cancel!(taskInfo),
              constraints: const BoxConstraints(minHeight: 32, minWidth: 32),
              icon: const Icon(
                Icons.stop,
                color: Colors.red,
              ))
        ],
      );
    } else if (taskInfo.status == DownloadTaskStatus.complete) {
      return Row(
        children: [
          const Text(
            'Ready',
            style: TextStyle(color: Colors.green),
          ),
          IconButton(
            onPressed: () => onActionTap!(taskInfo),
            constraints: const BoxConstraints(minHeight: 32, minWidth: 32),
            icon: const Icon(
              Icons.delete,
              color: Colors.red,
            ),
          ),
        ],
      );
    } else if (taskInfo.status == DownloadTaskStatus.canceled) {
      return Row(
        children: [
          const Text(
            'Canceled',
            style: TextStyle(color: Colors.red),
          ),
          IconButton(
            onPressed: () => onActionTap!(taskInfo),
            constraints: const BoxConstraints(minHeight: 32, minWidth: 32),
            icon: const Icon(
              Icons.cancel,
            ),
          ),
        ],
      );
    } else if (taskInfo.status == DownloadTaskStatus.failed) {
      return Row(
        children: [
          const Text(
            'Failed',
            style: TextStyle(color: Colors.red),
          ),
          IconButton(
            onPressed: () => onActionTap!(taskInfo),
            constraints: const BoxConstraints(minHeight: 32, minWidth: 32),
            icon: const Icon(
              Icons.refresh,
              color: Colors.green,
            ),
          ),
        ],
      );
    } else if (taskInfo.status == DownloadTaskStatus.enqueued) {
      return const Text(
        'Pending',
        style: TextStyle(color: Colors.orange),
      );
    } else {
      return null;
    }
  }

  //data!.status == DownloadTaskStatus.complete
  //? onTap!.call(data)
  // : null

  @override
  Widget build(BuildContext context) {
    return InkWell(
      onTap: //data!.status == DownloadTaskStatus.complete
          //     ? onTap!.call(data)
          null,
      child: Padding(
        padding: const EdgeInsets.all(20),
        child: Stack(
          children: [
            Row(
              children: [
                ConstrainedBox(
                    constraints: const BoxConstraints(
                        maxHeight: 200,
                        maxWidth: 200,
                        minHeight: 100,
                        minWidth: 100),
                    child: MoviePoster(movieId: int.tryParse(data!.movieID!)!)),
                const SizedBox(
                  width: 20,
                ),
                const Spacer(),
                _buildTrailing(data!)!
              ],
            ),
            if (data!.status == DownloadTaskStatus.running ||
                data!.status == DownloadTaskStatus.paused)
              Positioned(
                  left: 0,
                  right: 0,
                  bottom: 0,
                  child: LinearProgressIndicator(
                    value: data!.progress! / 100,
                  ))
          ],
        ),
      ),
    );
  }
mbakabilal
  • 189
  • 2
  • 12

0 Answers0