51

I am making flutter web app that should generate a file from user data. And have the option to download the output file.

But I can not find any options/packages which works for flutter web :(

Can someone please help me out?

Hamed
  • 5,867
  • 4
  • 32
  • 56
Pratik
  • 741
  • 1
  • 6
  • 8
  • 1
    What kind of data do you want to download CSV, Pdf etc ? – Deniz Bayar Feb 13 '20 at 09:37
  • Another problem I'm having is using a single code base for the Flutter and Web app. Trying to compile it on Flutter doesn't like the `dart:html` plugins that the answers below have. – Suragch Mar 05 '20 at 12:53
  • Is [this](https://stackoverflow.com/a/56611116/5675325) helpful to your case @Suragch? – Tiago Martins Peres Mar 10 '20 at 06:40
  • 1
    @TiagoMartinsPeres, Although I am not using Firebase, the source code in the answer's link was interesting. Thank you. I was hoping for a simpler solution, but Flutter Web is still in beta. Hopefully easier cross-platform solutions will be provided in the future. – Suragch Mar 11 '20 at 03:01
  • 1
    If your project supports mobile and web, I've explained the solution here: https://hesam-kamalan.medium.com/flutter-web-download-file-from-firebase-6a5c35ef7613 – Hesam Mar 16 '22 at 21:38

10 Answers10

57

Simple Code for redirecting to download URL

import 'dart:html' as html;
void downloadFile(String url) {
   html.AnchorElement anchorElement =  new html.AnchorElement(href: url);
   anchorElement.download = url;
   anchorElement.click();
}
Ahmed Ashour
  • 5,179
  • 10
  • 35
  • 56
vishwajit76
  • 2,300
  • 20
  • 16
  • 1
    Since this answer came late in the bounty period I don't have time to try it before the bounty expires. However, I'll assume that it works (but can you confirm that it would download an audio file without just playing it automatically?). I'm choosing this answer because it is easy to understand. – Suragch Mar 11 '20 at 02:56
  • 1
    This is specifically for flutter web. Is there any alternative for mobile app? – Razi Kallayi Apr 17 '20 at 20:35
  • In Android you can download file from url and save it to your phone storage using IO library. – vishwajit76 Apr 19 '20 at 04:47
  • 14
    This does not work. The browser opens the file rather than downloading it. – Stack Underflow Jul 15 '20 at 21:43
  • This is the only answer that has worked for me, so many articles, but this one worked. – Wesley Barnes May 06 '21 at 19:27
  • This worked but it only saves with the filename the url gave ... - on mac, chrome browser – Sha-agi May 12 '21 at 11:56
  • I think you can set the anchorElement.download = fileName to set a name unlike the url. – EzPizza Aug 19 '21 at 16:54
  • for me it worked, thanks a lot! the only point: I had to modify URL for running in release - like if (!isInDebug) {url = "assets/$url} just look on the file structure of the release build and you will get the point – Fellow7000 Sep 09 '21 at 14:44
  • Thanks @vishwajit76... it works perfect for Web, but an error appears if you have a cross-platform code because dart:html doesn't support Mobile. What do you recommend in this case? – David L Nov 30 '21 at 21:57
  • For cross-platform, you can use conditional import.see this post - https://medium.com/globant/support-flutter-cross-platform-b55ea3cccf41 – vishwajit76 Dec 01 '21 at 09:08
39

you can use package url_launcher with url_launcher_web

then you can do:

launchUrl(Uri.parse("data:application/octet-stream;base64,${base64Encode(yourFileBytes)}"));

EDIT: you don't need a plugin if you do this

download.dart:

import 'dart:convert';
// ignore: avoid_web_libraries_in_flutter
import 'dart:html';

void download(
  List<int> bytes, {
  String downloadName,
}) {
  // Encode our file in base64
  final _base64 = base64Encode(bytes);
  // Create the link with the file
  final anchor =
      AnchorElement(href: 'data:application/octet-stream;base64,$_base64')
        ..target = 'blank';
  // add the name
  if (downloadName != null) {
    anchor.download = downloadName;
  }
  // trigger download
  document.body.append(anchor);
  anchor.click();
  anchor.remove();
  return;
}

empty_download.dart:

void download(
  List<int> bytes, {
  String downloadName,
}) {
  print('I do nothing');
}

import and use:

import 'empty_download.dart'
if (dart.library.html) 'download.dart';

void main () {
  download('I am a test file'.codeUnits, // takes bytes
      downloadName: 'test.txt');
}
John Siu
  • 5,056
  • 2
  • 26
  • 47
pinguinitorawr
  • 391
  • 4
  • 3
  • 3
    I am surprised that this answer has no votes. It is perfect, this guy show two methods to acomplish what we need and no votes? Thank you very much! – GnRSlashSP Jan 21 '21 at 11:25
  • thanks for your answer! Could you explain me the purpose of the 'empty_download.dart' ? – Chris Dec 12 '21 at 18:00
  • @Chris That is alternative if `dart.library.html` is not available. The 1st 2 lines of the last part has to read together:`import 'empty_download.dart' if (dart.library.html) 'download.dart';` – John Siu Aug 04 '22 at 20:37
  • Thanks guy, url_launcher is best pratice – elgsylvain85 Sep 06 '22 at 17:35
  • 1
    I have this erros when try to use url_lancher: The method 'launchUrl' isn't defined for the type 'x'. Try correcting the name to the name of an existing method, or defining a method named 'launchUrl'. – Moises Conejo Mar 13 '23 at 20:49
  • This should be the accepted answer, works without any external package, and also produces no error when compiling for other platforms (for other platforms, download code needs to be implemented separately). – Soorya Jun 08 '23 at 10:49
  • It's really worked for me. I was struggling with this issue for a long time. Thank you so much. – Md. Sabik Alam Rahat Jun 17 '23 at 19:41
  • Be aware that It works on web but you will not be able to run your mobile app because of the unsupported imports "avoid_web_libraries_in_flutter" (if you have one) – Hwebservices Jun 21 '23 at 13:59
33

One way to trigger download is the adapt a common pattern used in "native" javascript, create an anchor element with the download attribute and trigger a click.

import 'dart:convert';
import 'dart:html';

main() {
  File file = // generated somewhere
  final rawData = file.readAsBytesSync();
  final content = base64Encode(rawData);
  final anchor = AnchorElement(
      href: "data:application/octet-stream;charset=utf-16le;base64,$content")
    ..setAttribute("download", "file.txt")
    ..click();
}

DAG
  • 6,710
  • 4
  • 39
  • 63
  • Wow this one saved me BIG TIME! Thank you so much. This code could instantly download a pdf file I generated in Flutter. – nipunasudha Apr 21 '20 at 15:34
  • There is just an issue with downloading in the mobile chrome version, the file is not stored. Working like a charm on mobile safari. – Roberto Juárez Dec 27 '21 at 20:46
  • In my case, I need to download images either jpeg or png. In mobile chrome, the file is not recognized as an image. Is there a way to indicate this on htref? – Kin Pu Apr 07 '22 at 18:44
12

I've found a solution that let me make an authorized request to get a file (with package http.dart) and then download the file using flutter web (with package dart:html). I don't know if someone other could need this, but I wasn't able to find a solution, so here is the code.

import 'package:http/http.dart';
import 'dart:html' as html;

...

var headers = {
   'Content-Type': 'application/octet-stream',
   'Accept': 'application/octet-stream',
   'Authorization' : 'Bearer [TOKEN HERE]'
};

Response res =
      await get(url, headers: headers); 

if (res.statusCode == 200) {
    final blob = html.Blob([res.bodyBytes]);
    final url = html.Url.createObjectUrlFromBlob(blob);
    final anchor = html.document.createElement('a') as html.AnchorElement
      ..href = url
      ..style.display = 'none'
      ..download = filename;
    html.document.body.children.add(anchor);

    anchor.click();

    html.document.body.children.remove(anchor);
    html.Url.revokeObjectUrl(url);
}
mmadd990
  • 131
  • 1
  • 4
11

A good workaround is to open the hosted file in a new tab using

import 'dart:html' as html;

openInANewTab(url){
  html.window.open(url, 'PlaceholderName');
}

Fits well for my use case.

Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
Shashwat Aditya
  • 390
  • 2
  • 11
  • 1
    This works for downloading an APK file. It doesn't work for downloading an audio file because the audio file starts playing automatically. – Suragch Mar 07 '20 at 00:51
  • 1
    Yes, I'm also trying to download a .json file and it too get's opened by the chrome tab. Trying to see if there is some way to use "download" tag in html5. – Eradicatore Apr 01 '20 at 16:50
  • 1
    @Eradicatore any luck? – Aidan Marshall Jun 21 '20 at 16:19
  • @AidanMarshall Yea I did get this working. I'll share some information here when I get a chance. I don't have the code handy right now – Eradicatore Jun 21 '20 at 19:36
  • 1
    @Eradicatore don't forget to share your treasure as soon as you can haha – Vae Aug 20 '20 at 23:12
2

I've used the followig to download file from a URL,

import 'dart:html' as html;

void downloadFileFromDownloadableLink(String url, String fileName) {
  html.AnchorElement anchorElement = html.AnchorElement(href: url);
  anchorElement.download = fileName;
  anchorElement.click();
}
0

Response to bounty:

the case of an audio file it just starts playing rather than downloading it

I've done same thing with video, but I'm sure it will work with audio as well. I assume, your generated audio is an array or blob

    import 'dart:js' as JS;
    import 'dart:html' as HTML;

    const mimeType = 'audio/wav'; // TODO: Pick a right format

    void downloadFile(List chunks) async {
        final blob = HTML.Blob(chunks, mimeType);
        final objectUrl = await HTML.Url.createObjectUrlFromBlob(blob);
        final a = HTML.AnchorElement();
        a.href = item.url;
        a.download = "my_audio_file_${DateTime.now()}.wav";
        HTML.document.body.append(a);
        a.click();
        // Wait for click event to be processed and cleanup
        await Future.delayed(Duration(milliseconds: 100));
        a.remove();
        HTML.Url.revokeObjectUrl(item.videoObjectUrl);
    }
Dmytro Rostopira
  • 10,588
  • 4
  • 64
  • 86
0

For me, vishwajit76's answer work for me. If you want the file to have a custom name once is downloaded then do the following

 `void downloadFile(String url) {
        html.AnchorElement anchorElement = html.AnchorElement(href: url);
        anchorElement.download = "myDocument.pdf"; //in my case is .pdf
        anchorElement.click();
      }`
hiramehg
  • 21
  • 3
0

All other solutions are correct but this solutions may disclose your storage path structure. Because downloaded file name includes complete path name of the file. If your path has uid number of firebase users, this may also cause a security breach. This may be an extreme example but it is a good practice to use uid numbers as on file path to create a more secure storage. You can create a temporary download folder as (Download/useremail) on root and copy the file intended to download inside of (Download/useremail) as below node.js code by firebase functions. This function is called when user click the download button from client. You can delete the temporary download folder later.

const functions = require("firebase-functions");
const {Storage} = require("@google-cloud/storage");

const storage = new Storage();

exports.downloadFile = functions.https.onCall(async (data, context)=> {
  await storage.
      bucket("my-bucket-name").
      file(data.filePath).
      copy(storage.
          bucket("my-bucket-name").
          file(`Download/${data.useremail}/${data.fileName}`));
});
isa
  • 398
  • 1
  • 3
  • 13
-3

You could also use the dio plugin, which supports this sort of thing out of the box. So you could just do:

response = await dio.download('url-to-downloadable', 'path-to-save-to');

And here's their API docs for the same.

Pranav Manoj
  • 69
  • 1
  • 6