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.
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 ?