781flyingdutchman / background_downloader

Flutter plugin for file downloads and uploads
Other
162 stars 76 forks source link

Failed to open file on Android java.lang.IllegalArgumentException: Failed to find configured root that contains: #349

Closed mu7mmd closed 2 months ago

mu7mmd commented 4 months ago

Open file from notification or on FileDownloader().openFile(task: _task) failed even when I used BaseDirectory.applicationSupport base directory.

Steps to reproduce the behavior:

  1. Set the configuration and notification settings to enable file opening on tap (tapOpensFile: true).
  2. Press the download button in the app.
  3. The notification appears, showing the progress of the download, and eventually completes successfully.
  4. The downloaded file is saved on the device using the moveToSharedStorage function.
  5. However, when I tap on the notification to open the file, nothing happens.
  6. The error occurs both when trying to open the file from the notification and when using the openFile(task: _task) function.

Notes

  1. This issue is specific to Android; it works fine on iOS.
  2. The problem persists even if I don't use the moveToSharedStorage function.
  3. I tried every BaseDirectory
  4. If I use openFile(filePath: 'identifier') it works fine when trigger the function, but on tap the notification still failed.
  5. I tried different files png and pdf

Expected behavior

When tap notification after download successful it opens the file and when trigger openFile(task: _task) also

Logs

Failed to open file /data/user/0/my.app.package/app_flutter/تطوير الجسد المشاعري.pdf: java.lang.IllegalArgumentException: Failed to find configured root that contains /data/data/my.app.package/app_flutter/تطوير الجسد المشاعري.pdf

Code

import 'dart:io' show Platform;

import 'package:background_downloader/background_downloader.dart';
import 'package:flutter/rendering.dart' show debugPrint;

import '../constants/key_enums.dart';

final class FileDownloaderService {
  FileDownloaderService() {
    _init();
  }

  DownloadTask? _task;

  final _fileDownloader = FileDownloader();

  void _init() {
    _configure();
    _configureNotification();
  }

  Future<void> _configure() {
    return _fileDownloader.configure(
      globalConfig: [(Config.requestTimeout, const Duration(minutes: 2))],
      androidConfig: [(Config.useCacheDir, Config.whenAble)],
    ).then((result) =>
        debugPrint('Download Service configuration result = $result'));
  }

  FileDownloader _configureNotification() {
    return _fileDownloader
        // .registerCallbacks(
        //   taskNotificationTapCallback: _notificationTapCallback,
        // )
        .configureNotificationForGroup(
          FileDownloader.defaultGroup,
          // When use 'enqueue' and a default group
          running: const TaskNotification(
            '{filename}',
            '{progress} - سرعة {networkSpeed} - متبقي {timeRemaining}',
          ),
          complete: const TaskNotification(
            '{filename}',
            'تم التنزيل بنجاح',
          ),
          error: const TaskNotification(
            '{filename}',
            'فشل التنزيل',
          ),
          paused: const TaskNotification(
            '{filename}',
            'تم إيقاف التنزيل',
          ),
          progressBar: true,
          tapOpensFile: true,
        )
        .configureNotification(
          // When use 'download' which is not the .defaultGroup
          // but the .await group so won't use the above config
          running: const TaskNotification(
            '{filename}',
            '{progress} - سرعة {networkSpeed} - متبقي {timeRemaining}',
          ),
          complete: const TaskNotification(
            '{filename}',
            'تم التنزيل بنجاح',
          ),
          error: const TaskNotification(
            '{filename}',
            'فشل التنزيل',
          ),
          paused: const TaskNotification(
            '{filename}',
            'تم إيقاف التنزيل',
          ),
          progressBar: true,
          tapOpensFile: true,
        ); // dog can also open directly from tap
    // .configureNotificationForGroup(
    //   'bunch',
    //   running: const TaskNotification(
    //     '{numFinished} out of {numTotal}',
    //     'Progress = {progress}',
    //   ),
    //   complete: const TaskNotification(
    //     "Done!",
    //     "Loaded {numTotal} files",
    //   ),
    //   error: const TaskNotification(
    //     'Error',
    //     '{numFailed}/{numTotal} failed',
    //   ),
    //   progressBar: false,
    //   groupNotificationId: 'notGroup',
    // )
  }

  /// Process the user tapping on a notification by printing a message
  // void _notificationTapCallback(Task task, NotificationType notificationType) {
  //   debugPrint(
  //     'Tapped notification $notificationType for taskId ${task.taskId}',
  //   );
  // }

  Future<void> download(
    String url,
    String fileName, {
    required void Function(DownloadStatus) onStatus,
    void Function(double)? onProgress,
  }) async {
    await _getPermission(PermissionType.notifications);

    onStatus(DownloadStatus.loading);

    _task = DownloadTask(
      url: url,
      filename: fileName,
      httpRequestMethod: HttpRequestMethod.get.name,
      baseDirectory: BaseDirectory.applicationDocuments,
      updates: Updates.statusAndProgress,
      retries: 3,
    );

    final result = await _fileDownloader.download(
      _task!,
      onProgress: onProgress,
      onStatus: (status) => onStatus(_getDownloadState(status)),
    );
    if (result.status == TaskStatus.complete) {
      await _moveToSharedStorage(fileName.split('.').last);
      onStatus(DownloadStatus.complete);
    }
  }

  Future<bool> enqueue(String url, String fileName) async {
    await _getPermission(PermissionType.notifications);
    _task = DownloadTask(
      url: url,
      filename: fileName,
      baseDirectory: BaseDirectory.applicationSupport,
      updates: Updates.statusAndProgress,
      retries: 3,
    );
    return _fileDownloader.enqueue(_task!);
  }

  void requireWiFi(
    RequireWiFi requireWiFi, {
    bool rescheduleRunningTasks = true,
  }) {
    _fileDownloader.requireWiFi(
      requireWiFi,
      rescheduleRunningTasks: rescheduleRunningTasks,
    );
  }

  static void listenDownloads(
    DownloadTask task, {
    required void Function(DownloadStatus) onStatusUpdate,
    required void Function(double) onProgressUpdate,
  }) {
    // Listen to updates and process
    FileDownloader().updates.listen((update) {
      if (update.task == task) {
        switch (update) {
          case TaskStatusUpdate():
            // [_getDownloadState] get TaskStatus.complete as DownloadStatus.loading
            // encase there is additional work to be done after the download
            // as in [download] method calling [_moveToSharedStorage] after download completed
            // then it should be [DownloadStatus.complete]
            if (update.status != TaskStatus.complete) {
              onStatusUpdate(_getDownloadState(update.status));
            } else {
              onStatusUpdate(DownloadStatus.complete);
            }

          case TaskProgressUpdate():
            onProgressUpdate(update.progress);
        }
      }
    });
  }

  void openFile() => _fileDownloader.openFile(task: _task);

  void cancel() => _fileDownloader.cancelTaskWithId(_task!.taskId);

  void pause() => _fileDownloader.pause(_task!);

  void resume() => _fileDownloader.resume(_task!);

  static DownloadStatus _getDownloadState(TaskStatus status) {
    return switch (status) {
      TaskStatus.enqueued ||
      TaskStatus.waitingToRetry ||
      TaskStatus.complete =>
        DownloadStatus.loading,
      TaskStatus.running => DownloadStatus.downloading,
      _ => DownloadStatus.failed,
    };
  }

  Future<void> _moveToSharedStorage(String extension) async {
    // add to photos library and print path
    // If you need the path, ask full permissions beforehand by calling
    final permissionType = Platform.isIOS
        ? PermissionType.iosChangePhotoLibrary
        : PermissionType.androidSharedStorage;
    var auth = await _fileDownloader.permissions.status(permissionType);
    if (auth != PermissionStatus.granted) {
      auth = await _fileDownloader.permissions.request(permissionType);
    }
    if (auth == PermissionStatus.granted) {
      final sharedStorage = switch (extension) {
        'png' || 'jpg' || 'jpeg' => SharedStorage.images,
        'mp4' => SharedStorage.video,
        _ => SharedStorage.downloads,
      };
      final identifier =
          await _fileDownloader.moveToSharedStorage(_task!, sharedStorage);
      print(identifier);
      if (identifier != null) {
        // On Android the file path is the [identifier]
        // To get the path of the file on iOS, you need to use the identifier:
        // final path = await _fileDownloader.pathInSharedStorage(
        //   identifier,
        //   sharedStorage,
        // );
        debugPrint('Path in Photos Library = $identifier');
      } else {
        debugPrint(
            'Could not add file to Photos Library, likely because permission denied,');
      }
    } else {
      debugPrint('Photo Library permission not granted');
    }
  }

  /// Attempt to get permissions if not already granted
  Future<void> _getPermission(PermissionType permissionType) async {
    var status = await _fileDownloader.permissions.status(permissionType);
    if (status != PermissionStatus.granted) {
      if (await _fileDownloader.permissions
          .shouldShowRationale(permissionType)) {
        debugPrint('Showing some rationale');
      }
      status = await _fileDownloader.permissions.request(permissionType);
      debugPrint('Permission for $permissionType was $status');
    }
  }
}

Device Details Pixel 8 pro • Android: 14

Library Details Version: 8.5.2

Flutter Details Flutter 3.22.0 • channel stable • Dart 3.4.0

github-actions[bot] commented 3 months ago

This issue is stale because it has been open for 14 days with no activity.

leonardchikenzen commented 3 months ago

Same with me, did you find any solution

781flyingdutchman commented 3 months ago

Not sure if this is it,m but per this: "Note that on Android, files stored in the BaseDirectory.applicationDocuments cannot be opened. You need to download to a different base directory (e.g. .applicationSupport) or move the file to shared storage before attempting to open it."

leonardchikenzen commented 3 months ago

But even after moving to shared storage, it was not opening.

github-actions[bot] commented 3 months ago

This issue is stale because it has been open for 14 days with no activity.

github-actions[bot] commented 2 months ago

This issue was closed because it has been inactive for 7 days since being marked as stale.