Reputation: 2260
Just about any mobile app framework seems to have the ability to download files to the "Downloads" directory, except for Flutter. Or is it just me?
Since Android SDK 29 it appears Flutter (Android) has taken the direction of only allowing files to be downloaded to the scoped application directory or the application's temporary cache directory. I could be totally wrong on this, which is why I am posting on SO, in the hopes I am.
You can download on Android, but the problem is you would have to write a tutorial for a user to try to find anything you downloaded because of where you can download files in Flutter. Either to the inaccessible except by app applications directory or the impossible to find application temp cache directory.
On Slack's app I can download a file and it will save to Downloads. Not a big deal.
There is lots of advice on SO about flutter packages that are outdated:
getExternalStorageDirectory()
function. You'd assume it would return external storage options on Android, but all it currently returns are the application document directory (not accessible to users outside the app) and the temp application cache directory, which is impossible for a user to find without a tutorial./storage/emulated/0/Download
does not work Oct. 30, 2021. No permission to write even if you have been granted storage
permission.Flutter needs to be able to download files on Android to the Downloads or media directories. There are plenty of legit reasons that you might download a file from a Flutter app that needs to be accessible in external storage that is public and can easily be found through Android's Files app.
Anyway, what options does a Flutter dev have here Oct. 2021 to save files on Android to the Downloads directory??? Btw, just a default on iOS.
Right now, it appears on Android the best option for Flutter devs is to use a something like url_launcher and craft a url that forces a download, which will in turn access the browser's download manager. Is the best scenario for Android right now?
Finally, I love Flutter, but this particular area seems to be needing some love so Flutter devs don't fall behind other frameworks out there.
Upvotes: 4
Views: 6362
Reputation: 2260
For Oct 2021 Flutter it turns out there is a great solution for this for Android. The missing package I needed was the flutter_file_dialog package, which is using Kotlin to add the needed programming to save files wherever the Flutter app user wants to on their Android device, including Downloads.
Packages I used:
For android the flow is:
getTemporaryDirectory()
get the application's scoped cache directory path.Code snippets to see flow:
try {
/// Verify we have storage permissions first.
/// Code snippet showing me calling my devicePermissions service provider
/// to request "storage" permission on Android.
await devicePermissions
.storagePermissions()
.then((granted) async {
/// Get short lived url from google cloud storage for non-public file
final String? shortLivedUrl...
if (shortLivedUrl == null) {
throw Exception('Could not generate a '
'downloadable url. Please try again.');
}
final String url = shortLivedUrl;
print(url);
/// Get just the filename from the short lived (super long) url
final String filename =
Uri.parse(url).path.split("/").last;
print('filename: $filename');
Directory? directory;
if (Platform.isIOS) {
directory = await getDownloadsDirectory();
print(directory?.path);
} else if (Platform.isAndroid) {
/// For Android get the application's scoped cache directory
/// path.
directory = await getTemporaryDirectory();
}
if (directory == null) {
throw Exception('Could not access local storage for '
'download. Please try again.');
}
print(
'Temp cache save path: ${directory.path}/$filename');
/// Use Dio package to download the short lived url to application cache
await dio.download(
url,
'${directory.path}/$filename',
onReceiveProgress: _showDownloadProgress,
);
/// For Android call the flutter_file_dialog package, which will give
/// the option to save the now downloaded file by Dio (to temp
/// application cache) to wherever the user wants including Downloads!
if (Platform.isAndroid) {
final params = SaveFileDialogParams(
sourceFilePath: '${directory.path}/$filename');
final filePath =
await FlutterFileDialog.saveFile(params: params);
print('Download path: $filePath');
}
});
} on DevicePermissionException catch (e) {
print(e);
await OverlayMessage.showAlertDialog(
context,
title: 'Need Storage Permission',
message: devicePermissions.errorMessage,
barrierDismissible: true,
leftAction: (context, controller, setState) {
return TextButton(
child: Text('Cancel'),
onPressed: () async {
controller.dismiss();
},
);
},
rightAction: (context, controller, setState) {
return TextButton(
child: Text('Open App Settings'),
onPressed: () async {
controller.dismiss();
Future.delayed(Duration.zero, () async {
/// Using permission_handler package we can easily give
/// the user the option to tap and open settings from the
/// app and manually allow storage.
await devicePermissions.openManualAppSettings();
});
},
);
},
);
} catch (e) {
print(e.toString());
}
Upvotes: 4