I had a similar problem while trying to implement a download feature that downloads and saves a CSV from the backend to the local app location.
There are a few reasons why url_launcher wouldn't work for my case some of them are
- The downloads here will be authenticated (using JWT in the backend)
which requires a header value of access-token which cannot be set in url_launcher since the headers will already be set when the user launches the URL in the launch view.
- The return type is not a web view file rather it is a byte-stream data from the backend. If this is your logic then url_launcher will just show a white page and crash right away since the byte stream cannot be opened in web view.
Hence I would like to recommend this strategy of writing files as bytes into the device using dart:io functions
the dependencies we would need are
- permission_handler: ^10.2.0
- api_cache_manager: ^1.0.1 (only if you are using the local sqlite storage)
Backend logic:
const functionToGetCSV = async (req, res) => {
try {
const data = await inventory.findAll({raw: true});
sendCSV(res, "Backend Data" + dateForFilename(), data);
return;
} catch (error) {
logger.error("server error", { error });
return res.status(500).send({ message: "Server Error. Try again." });
}
}
Frontend logic:
I had the following export option in the three-dot button which will initiate the download process
Export button
This gets executed as soon as the export is clicked
SelectedItem(BuildContext context, int item) {
if (item == 2) {
_launchUrl();
} else if (item == 3) {
_logout();
}}
The launch URL logic
Future<void> _launchUrl() async {
String baseUrl = 'http://${Global.endpoint}';
var url = Uri.parse('$baseUrl/api/csv/get-inventory');
var token = await APICacheManager().getCacheData("login_details");
var token1 = json.decode(token.syncData);
var client = http.Client();
final response = await client.get(url, headers: <String, String>{
'Access-Token': token1["data"]["token"],
});
if (await _requestPermission(Permission.storage)) {
try {
// ignore: await_only_futures
DateTime datetime = await DateTime.now();
// ignore: unnecessary_new
File file = await new File(
'storage/emulated/0/ERP/${datetime.hour.toString()}${datetime.minute.toString()}${datetime.second.toString()}get-inventory.csv')
.create(recursive: true);
await file.writeAsBytes(response.bodyBytes,
flush: true, mode: FileMode.write);
final snackBar = SnackBar(
backgroundColor: const Color.fromARGB(255, 36, 198, 30),
content: Text(
'File Saved! ${file.path}'));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
} catch (err) {
final snackBar = SnackBar(
backgroundColor: const Color.fromARGB(255, 198, 30, 30),
content: Text("Error saving file! ${err.toString()}"));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
} else {
_launchUrl();
}}
Make sure:
To keep the await for DateTime.Now() as I found in some emulators there might be a delay in calling the DateTime.Now() (ps: yes it was weird as the bug vanished after I gave await for this function)
To keep the new File and .create(recursive:true); since this would create com.package_name folder in case you don't have one by default
To add this request permission function
Future<bool> _requestPermission(Permission permission) async {
var res = await permission.request();
if (res == PermissionStatus.granted) {
return true;
} else {
return false;
}}
you will be able to see your downloads saved here
Internal storage has your app folder
Inside that you can find your saved files
Do let me know if this works for you as this will be very easy for downloaded authenticated files.