user14757651
user14757651

Reputation: 107

Create Zip file from bytes files Flutter web

I use firebase cloud storage to store some files, then, i have to put them in a zip file and download it, i have to do this ONLY on web, and thats the hard part. im doing it like this now:

FloatingActionButton(
    child: const Icon(Icons.download),
    onPressed: () async {
      List<int>? bytes;
      webarc.ZipEncoder encoder = webarc.ZipEncoder();
      webarc.Archive archive = webarc.Archive();

      try {
        List<String> links = await Database.formLinks();

        for (String link in links) {
          String fileName = link.substring(link.lastIndexOf('/') + 1);
          http.Response response = await http.get(
            Uri.parse(link),
            headers: headers,
          );
          webarc.ArchiveFile file = webarc.ArchiveFile.stream(
            fileName,
            response.bodyBytes.elementSizeInBytes,
            response.bodyBytes.toList(),
          );
          archive.addFile(file);
        }
        webarc.OutputStream outputStream = webarc.OutputStream(
          byteOrder: webarc.LITTLE_ENDIAN,
        );
        bytes = encoder.encode(
          archive,
          level: webarc.Deflate.BEST_COMPRESSION,
          modified: DateTime.now(),
          output: outputStream,
        );
      } catch (e) {
        print(e);
        Fluttertoast.showToast(
          msg: 'Errore nel download dello zip.',
          toastLength: Toast.LENGTH_LONG,
          gravity: ToastGravity.BOTTOM,
        );
        return;
      }
      if (bytes == null) {
        Fluttertoast.showToast(
          msg: 'Errore nel download dello zip. bytes nulli.',
          toastLength: Toast.LENGTH_LONG,
          gravity: ToastGravity.BOTTOM,
        );
        return;
      }

      String zipFileName = 'forms.zip';

      await FileSaver.instance.saveFile(
        zipFileName,
        Uint8List.fromList(bytes),
        'zip',
        mimeType: MimeType.ZIP,
      );

      Fluttertoast.showToast(
        msg: 'Zip downloadato correttamente.',
        toastLength: Toast.LENGTH_LONG,
        gravity: ToastGravity.BOTTOM,
      );
    },
  ),

its all perfect, except for the fact that i download the zip file, but the zip file is empty.

I tryied doing this in many different ways, downloading it with html package, anchoring and clicking a div, but now i think that the package doesnt work, how can i do this? Any help will be accepted, thanks.

P.S. Here is the database.formLinks function:

 static Future<List<Reference>> getCompiledForms() async =>
  (await formsRef.listAll()).items;

 static Future<List<String>> formLinks() async {
final List<Reference> forms = await getCompiledForms();
final List<String> links = [];
for (final form in forms) {
  String url = await form.getDownloadURL();
  links.add(url);
}
return links;

}
And those are the packages that i've used in those functions:

archive: ^3.1.2 (As webarc)

http: ^0.13.3 (as http)

file_saver: ^0.0.10

fluttertoast: ^8.0.7

cloud_firestore: ^2.3.0

Upvotes: 6

Views: 3056

Answers (2)

user22264456
user22264456

Reputation: 197

I took the code from AmateurCoder that was no longer working and modified it a bit to make it work in 2024. I also added a small usage example.

Modified Code for archive v3.X

downloadFilesAsZIP({required BuildContext context, required Map<String, InputStreamBase> files, required String zipFileName}) {
  var encoder = ZipEncoder();
  var archive = Archive();
  for (var file in files.entries) {
    var archiveFiles = ArchiveFile.stream(
      file.key,
      file.value.length,
      file.value,
    );
    archive.addFile(archiveFiles);
  }
  var outputStream = OutputStream(
    byteOrder: LITTLE_ENDIAN,
  );
  var byteList = encoder.encode(archive, level: Deflate.BEST_COMPRESSION, output: outputStream);
  if (byteList == null) return;
  final Uint8List uint8List = Uint8List.fromList(byteList);

  downloadFile(zipFileName, uint8List);
}

downloadFile(String fileName, Uint8List bytes) {
  final blob = html.Blob([bytes]);
  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);

// download
  anchor.click();

// cleanup
  html.document.body?.children.remove(anchor);
  html.Url.revokeObjectUrl(url);
}

Usage Example for archive v3.X

ElevatedButton(
  onPressed: () {
    List<int> fileData = utf8.encode('Test');
    downloadFilesAsZIP(
      context: context,
      zipFileName: 'test.zip',
      files: {
        'test.txt': InputStream(fileData),
      },
    );
  },
  child: const Text('Download'),
),

Modified Code for archive v4.X

void downloadFilesAsZIP({required BuildContext context, required Map<String, InputStream> files, required String zipFileName}) {
  var encoder = ZipEncoder();
  var archive = Archive();
  for (var file in files.entries) {
    var archiveFiles = ArchiveFile.stream(
      file.key,
      file.value,
    );
    archive.addFile(archiveFiles);
  }
  var outputStream = OutputMemoryStream(
    byteOrder: ByteOrder.littleEndian,
  );
  var byteList = encoder.encode(archive, level: DeflateLevel.bestCompression, output: outputStream);
  final Uint8List uint8List = Uint8List.fromList(byteList);
  downloadFile(zipFileName, uint8List);
}

void downloadFile(String fileName, Uint8List bytes) {
  final blob = html.Blob([bytes]);
  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);

// download
  anchor.click();

// cleanup
  html.document.body?.children.remove(anchor);
  html.Url.revokeObjectUrl(url);
}

Usage Example for archive v4.X

ElevatedButton(
  onPressed: () {
    List<int> fileData = utf8.encode('Test');
    downloadFilesAsZIP(
      context: context,
      zipFileName: 'test.zip',
      files: {
        'test.txt': InputMemoryStream(fileData),
      },
    );
  },
  child: const Text('Download'),
),

Upvotes: 0

AmateurCoder
AmateurCoder

Reputation: 208

I was struck with a similar issue this week and was able to figure out the problem. I am using html download using anchor.click() method.

Download files as zip method takes input as list of file names as String and list of file data as Uint8List.

Try this and let me know if this works for you!

  _downloadFilesAsZIP(context, List<String> filenames, files) {
    var encoder = ZipEncoder();
    var archive = Archive();
    ArchiveFile archiveFiles = ArchiveFile.stream(
      filenames[0].toString(),
      files[0].lengthInBytes,
      files[0],
    );
    print(archiveFiles);
    archive.addFile(archiveFiles);
    var outputStream = OutputStream(
      byteOrder: LITTLE_ENDIAN,
    );
    var bytes = encoder.encode(archive,
        level: Deflate.BEST_COMPRESSION, output: outputStream);
    print(bytes);
    downloadFile("out.zip", bytes);
  }

  downloadFile(String fileName, Uint8List bytes) {

    final blob = html.Blob([bytes]);
    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);

// download
    anchor.click();

// cleanup
    html.document.body.children.remove(anchor);
    html.Url.revokeObjectUrl(url);
  }

Upvotes: 8

Related Questions