fluttercommunity/flutter_downloader

Load task throws null check error

TheArslan opened this issue · 11 comments

await FlutterDownloader.loadTasks();

throw null check exception when any file is in process.

@TheArslan can you please post some code so i can help you ? more info

@salmaahhmed scenario is when I enque any task. and go back to previous screen and come back again on that screen and load tasks. I noticed when I loadTasks it throughs null check exception on " await FlutterDownloader.loadTasks();". I further debug your plugin and I found in loadTasks function your method channel call " final result = await _channel.invokeMethod<List>('loadTasks');" not fetch the details of those tasks whose status is 2. tasks having status of 2 result com empty object {} and when you try to Map this empty object in DownloadTask class it thows null check exception.

Screenshot 2023-09-25 at 2 59 18 PM

here can you the last index have 0 element and and this only comes if the status is 2

The things is why other tasks not come in list who have status other than 3.

and also when I kill app downloading stops

@TheArslan android or iOS ?

Ios

@TheArslan can you post some pieces of ur code please ?

class SessionDetailsScreen extends StatefulWidget with WidgetsBindingObserver {
final SessionsData sessionsData;

const SessionDetailsScreen({
super.key,
required this.sessionsData,
});

@OverRide
State createState() => _SessionDetailsScreenState();
}

class _SessionDetailsScreenState extends State {
List? sessionInfo;
late bool _showBottomSheet;
late bool _showContent;
late bool _permissionReady;

late String _localPath;
final ReceivePort _port = ReceivePort();

@OverRide
void initState() {
super.initState();
_bindBackgroundIsolate();

FlutterDownloader.registerCallback(downloadCallback, step: 1);
_showBottomSheet = false;
_showContent = false;
_permissionReady = false;
_prepare();

}

@pragma('vm:entry-point')
static void downloadCallback(
String id,
int status,
int progress,
) {
print(
'Callback on background isolate: '
'task ($id) is in status ($status) and process ($progress)',
);

IsolateNameServer.lookupPortByName('downloader_send_port')
    ?.send([id, status, progress]);

}

void _bindBackgroundIsolate() {
final isSuccess = IsolateNameServer.registerPortWithName(
_port.sendPort,
'downloader_send_port',
);
if (!isSuccess) {
_unbindBackgroundIsolate();
_bindBackgroundIsolate();
return;
}
_port.listen((dynamic data) {
final taskId = (data as List)[0] as String;
final status = DownloadTaskStatus.fromInt(data[1] as int);
final progress = data[2] as int;
if (status == DownloadTaskStatus.complete) {
PreferencesController.removeKey(
taskId,
);
}

  if (sessionInfo != null && sessionInfo!.isNotEmpty) {
    final taskIndex =
        sessionInfo?.indexWhere((task) => task.taskId == taskId);
    if (taskIndex != null && taskIndex != -1) {
      final task = sessionInfo![taskIndex];
      setState(() {
        task
          ..taskId = taskId
          ..status = status
          ..progress = progress;
      });
    }
  }
});

}

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

Future _prepare() async {
List? tasks;

try {
  tasks = await FlutterDownloader.loadTasks();
} catch (e) {
  debugPrint(e.toString());
}

sessionInfo = [];
if (tasks == null) {
  sessionInfo!.addAll(
    widget.sessionsData.sessionsList?.first.sessions?.map((session) =>
            TaskInfo(session: session, link: session.privateUrl)) ??
        [],
  );
  setState(() {
    _showContent = true;
  });

  return;
}

sessionInfo!.addAll(
  widget.sessionsData.sessionsList?.first.sessions?.map((session) =>
          TaskInfo(session: session, link: session.privateUrl)) ??
      [],
);

for (final task in tasks) {
  for (final info in sessionInfo!) {
    String? downloadedVideoId =
        extractVimeoVideoIdFromDownloadedUrl(task.url);
    String? videoId = extractVimeoVideoIdFromPrivatedUrl(info.link);

    if (task.url == null) {
      var keyFromPref =
          PreferencesController.getcheckValue(info.session!.id!);

      if (keyFromPref != null &&
          info.status == DownloadTaskStatus.undefined) {
        info
          ..taskId = keyFromPref
          ..link = info.link
          ..status = task.status
          ..progress = task.progress ?? 0;
      }
    } else if (downloadedVideoId == videoId) {
      PreferencesController.removeKey(
        task.taskId!,
      );
      info
        ..taskId = task.taskId
        ..link = task.url
        ..status = task.status
        ..progress = task.progress;
    }
  }
}

_permissionReady = await _checkPermission();
if (_permissionReady) {
  await _prepareSaveDir();
}

setState(() {
  _showContent = true;
});

}

String? extractVimeoVideoIdFromDownloadedUrl(String? vimeoUrl) {
if (vimeoUrl == null) return null;
RegExp regex = RegExp(r"/download/(\d+)/");
Match? match = regex.firstMatch(vimeoUrl);

if (match != null && match.groupCount >= 1) {
  return match.group(1)!;
}

return null;

}

bool allDownloaded() {
if (sessionInfo != null) {
for (var session in sessionInfo!) {
if (session.status == DownloadTaskStatus.undefined) {
return false;
}
}
}
return true;
}

String? extractVimeoVideoIdFromPrivatedUrl(String? url) {
if (url == null) return null;
Uri uri = Uri.parse(url);
List pathSegments = uri.pathSegments;

// The last segment is the video ID
if (pathSegments.isNotEmpty) {
  return pathSegments.last;
} else {
  return null;
}

}

Future _requestDownload(List tasks) async {
var videoId = tasks.map((e) {
e.status = e.status == DownloadTaskStatus.undefined
? DownloadTaskStatus.enqueued
: e.status;
return extractVimeoVideoIdFromPrivatedUrl(e.link);
}).toList();
setState(() {});
if (videoId.isNotEmpty) {
var urls = await fetchDownloadUrl(videoId);

  if (urls != null && urls.isNotEmpty) {
    for (var task in tasks) {
      if (task.status == DownloadTaskStatus.enqueued) {
        String? id =
            extractVimeoVideoIdFromPrivatedUrl(task.session?.privateUrl);

        var index = urls.indexWhere((element) => element.videoId == id);

        task.link = urls[index].url;

        task.taskId = await FlutterDownloader.enqueue(
          url: task.link!,
          headers: {'auth': 'test_for_sql_encoding'},
          savedDir: _localPath,
          saveInPublicStorage: false,
        );
        if (task.taskId != null && task.session!.id != null) {
          AppAlerts.showMessageSnackBar(AppStrings.keepAppOpen);
          PreferencesController.saveKey(task.taskId!, task.session!.id!);
        }
      }
    }
    if (tasks.length == 1) {
      downloadSingleSessionVideo(widget.sessionsData, tasks.first.session!);
    } else {
      downloadCompleteSession(
        widget.sessionsData,
      );
    }
  } else {
    for (var task in tasks) {
      task.status = DownloadTaskStatus.undefined;
    }
    AppAlerts.showMessageSnackBar(AppStrings.someThingWentWrong);
    setState(() {});
  }
}

}

Future<List?> fetchDownloadUrl(List<String?> videoIds) async {
if (videoIds.isEmpty) return null;
final Repository repository = Repository();
try {
// emit(JustTrainAndLearnDetailLoadingState());
final apiResponse = await repository.getDownloadVideosResponse(videoIds);
if (isApiSuccess(apiResponse) &&
apiResponse.message?.toLowerCase() !=
AppStrings.unauthenticated.toLowerCase()) {
return apiResponse.data;
} else if (apiResponse.message!.toLowerCase() ==
AppStrings.unauthenticated.toLowerCase()) {
if (context.mounted) {
AppNavigator.pushReplacement(context, const SigninScreen());
}
}
} catch (e) {
return null;
}
return null;
}

void downloadSingleSessionVideo(
SessionsData sessionData, Sessions selectedSessions) async {
var savedData = PreferencesController.getSavedSessionsData();
bool categoryPresent = false;

var dataToBeSaved = (savedData as List?)?.map((element) {
      var session = SessionsData.fromJson(element);

      if (sessionData.category?.id == session.category?.id) {
        categoryPresent = true;

        var indexWhereValue = session.sessionsList
            ?.indexWhere((e) => e.id == sessionData.sessionsList?.first.id);
        if (indexWhereValue == -1) {
          session.sessionsList?.addAll(sessionData.sessionsList ?? []);
        } else if (indexWhereValue != null) {
          var indexWhereSelectedValuePresent = session
              .sessionsList?[indexWhereValue].sessions
              ?.indexWhere((element) => element.id == selectedSessions.id);
          if (indexWhereSelectedValuePresent == -1) {
            session.sessionsList?[indexWhereValue].sessions
                ?.add(selectedSessions);
          }
        }
      }

      return session.toJson();
    }).toList() ??
    <Map<String, dynamic>>[];
if (!categoryPresent) {
  var modifiedSessionData = SessionsData.deepCopy(sessionData);
  modifiedSessionData.sessionsList?[0].sessions = [selectedSessions];
  // SessionsData modifiedSessionData = SessionsData(
  //     category: sessionData.category,
  //     data: [...sessionData.sessionsList ?? []]);
  // // var=  sessionData.sessionsList?.map((e) => List.from(e)).toList();
  // modifiedSessionData.sessionsList?.first.sessions?.clear();
  // modifiedSessionData.sessionsList?.first.sessions?.add(selectedSessions);
  dataToBeSaved.add(modifiedSessionData.toJson());
}

PreferencesController.saveSessionsData(dataToBeSaved);

}

void downloadCompleteSession(SessionsData sessionData) {
var savedData = PreferencesController.getSavedSessionsData();
bool categoryPresent = false;

var dataToBeSaved = (savedData as List?)?.map((element) {
      var session = SessionsData.fromJson(element);

      if (sessionData.category?.id == session.category?.id) {
        categoryPresent = true;

        var indexWhereValue = session.sessionsList
            ?.indexWhere((e) => e.id == sessionData.sessionsList?.first.id);
        if (indexWhereValue == -1) {
          session.sessionsList?.addAll(sessionData.sessionsList ?? []);
        } else if (indexWhereValue != null) {
          session.sessionsList?[indexWhereValue] =
              sessionData.sessionsList!.first;
        }
      }

      return session.toJson();
    }).toList() ??
    <Map<String, dynamic>>[];
if (!categoryPresent) {
  dataToBeSaved.add(sessionData.toJson());
}

PreferencesController.saveSessionsData(dataToBeSaved);

}

Future _pauseDownload(TaskInfo task) async {
await FlutterDownloader.pause(taskId: task.taskId!);
}

Future _resumeDownload(TaskInfo task) async {
final newTaskId = await FlutterDownloader.resume(taskId: task.taskId!);
task.taskId = newTaskId;
}

Future _retryDownload(TaskInfo task) async {
final newTaskId = await FlutterDownloader.retry(taskId: task.taskId!);
task.taskId = newTaskId;
}

Future _openDownloadedFile(TaskInfo? task) async {
final taskId = task?.taskId;
if (taskId == null) {
return false;
}

return FlutterDownloader.open(taskId: taskId);

}

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

void showBottomSheet() {
_showBottomSheet = !_showBottomSheet;
setState(() {});
}

Future _checkPermission() async {
if (Platform.isIOS) {
return true;
}

if (Platform.isAndroid) {
  final info = await DeviceInfoPlugin().androidInfo;
  if (info.version.sdkInt > 28) {
    return true;
  }

  final status = await Permission.storage.status;
  if (status == PermissionStatus.granted) {
    return true;
  }

  final result = await Permission.storage.request();
  return result == PermissionStatus.granted;
}

throw StateError('unknown platform');

}

Future _prepareSaveDir() async {
_localPath = (await _getSavedDir())!;
final savedDir = Directory(_localPath);
if (!savedDir.existsSync()) {
await savedDir.create();
}
}

Future<String?> _getSavedDir() async {
String? externalStorageDirPath;

if (Platform.isAndroid) {
  try {
    externalStorageDirPath = await AndroidPathProvider.downloadsPath;
  } catch (err, st) {
    print('failed to get downloads path: $err, $st');

    final directory = await getExternalStorageDirectory();
    externalStorageDirPath = directory?.path;
  }
} else if (Platform.isIOS) {
  // var dir = (await _dirsOnIOS)[0]; // temporary
  // var dir = (await _dirsOnIOS)[1]; // applicationSupport
  // var dir = (await _dirsOnIOS)[2]; // library
  var dir = (await _dirsOnIOS)[3]; // applicationDocuments
  // var dir = (await _dirsOnIOS)[4]; // downloads

  dir ??= await getApplicationDocumentsDirectory();
  externalStorageDirPath = dir.absolute.path;
}

return externalStorageDirPath;

}

Future<List<Directory?>> get _dirsOnIOS async {
final temporary = await getTemporaryDirectory();
final applicationSupport = await getApplicationSupportDirectory();
final library = await getLibraryDirectory();
final applicationDocuments = await getApplicationDocumentsDirectory();
final downloads = await getDownloadsDirectory();

final dirs = [
  temporary,
  applicationSupport,
  library,
  applicationDocuments,
  downloads
];

return dirs;

}

@OverRide
void dispose() {
_unbindBackgroundIsolate();

super.dispose();

}

@OverRide
Widget build(BuildContext context) {

.................

The code is almost your example code.

ThankYou @salmaahhmed the issue is fixed now in latest update. But downloading cancel when I kill the IOS app.

@TheArslan can you give me more details ?