How can I save a downloaded file with Capacitor in Ionic4 Angular?

Hi everyone!

I have recently published my Ionic 4 Angular App both as web app and as native Android app.
In the native Android app, everything works fine, except saving a downloaded file.

To download and save a file, I’ve always used file-saver npm package as follows (this is a shared service that I invoke every time I have to download something, from a PDF to an image, etc…):

import { saveAs } from 'file-saver';

// ...

saveGenericFile(api: string, fileinfos: any, idFile: string): any {
    let mediaType = 'application/pdf';
    let fileName = '';

    if (fileinfos != null) {
      mediaType = fileinfos.contentType;
      fileName = fileinfos.fileName;
    }

    const headers = this.base.commonHeader;
    const url = this.baseUrl + api + '?id=' + idFile;
    this.http.post(url, null, { headers, responseType: 'blob' }).subscribe(
      (response) => {
        // tslint:disable-next-line: prefer-const
        let blob = new Blob([response], { type: mediaType });
        saveAs(blob, fileName);
      }, e => {
        console.error(e);
        this.toastsvc.generateToast('ERROR! An error occurred while saving this File, try later or contact support', 'danger');
      }, () => {
        /* do nothing */
      }
    );
  }

As I said above, this code snippet works fine, but just when I have to save something from the web release.

The only online examples I was able to find were all about Cordova and/or previous/deprecated versions.
About Capacitor, I just found this documentation and from that I just found this code snippet:

import { Plugins, FilesystemDirectory, FilesystemEncoding } from '@capacitor/core';

const { Filesystem } = Plugins;

fileWrite() {
  try {
    Filesystem.writeFile({
      path: 'secrets/text.txt',
      data: "This is a test",
      directory: FilesystemDirectory.Documents,
      encoding: FilesystemEncoding.UTF8
    });
  } catch(e) {
    console.error('Unable to write file', e);
  }
}

But the issue is that my function above returns a blob and this one only accept a string for data.

So, is there any Capacitor-Native equivalent feature that I can use to download (and save) Blob files both when running as web-app and when running as Android native app?

Thanks in advance!


UPDATE

I’ve also tried the following, but it’s not working:

import { Platform } from '@ionic/angular';
//...

useCordovaDl: boolean = false;
  constructor(
    private http: HttpClient,
    private base: BaseService,
    private requestsService: RequestsService,
    private toastsvc: ToastService,
    private platform: Platform
  ) {
    this.baseUrl = base.baseUrl;
    if (this.platform.is('ios') || this.platform.is('android')) {
      this.useCordovaDl = true;
    }
  }

//...

saveGenericFile(api: string, fileinfos: any, gidFile: string): any {
    let mediaType = 'application/pdf';
    let fileName = '';

    if (fileinfos != null) {
      mediaType = fileinfos.contentType;
      fileName = fileinfos.fileName;
    }

    const headers = this.base.commonHeader;
    const url = this.baseUrl + api + '?id=' + gidFile;
    this.http.post(url, null, { headers, responseType: 'blob' }).subscribe(
      (response) => {
        if (!this.useCordovaDl) {
          // tslint:disable-next-line: prefer-const
          let blob = new Blob([response], { type: mediaType });
          saveAs(blob, fileName);
        } else {
          this.blobFileWrite(fileName, response);
        }
      }, e => {
        console.error(e);
        this.toastsvc.generateToast('ERROR! An error occurred while saving this File, try later or contact support', 'danger');
      }, () => {
        /* do nothing */
      }
    );
  }

  blobFileWrite(filename: string, blobfile: Blob) {
    const reader = new FileReader();

    // This fires after the blob has been read/loaded.
    reader.addEventListener('loadend', (e: any) => {
      const text = e.srcElement.result;
      this.fileWrite(filename, text);
    });

    // Start reading the blob as text.
    reader.readAsText(blobfile);
  }

  fileWrite(filename: string, filedata: string) {
    try {
      Filesystem.writeFile({
        path: filename,
        data: filedata
        // ,
        // directory: FilesystemDirectory.Documents,
        // encoding: FilesystemEncoding.UTF8
      });
    } catch (e) {
      console.error('Unable to write file', e);
    }
  }

I had the same problem in ios platform. The only solution that I have found is to use the Cordova file opener plugin.

try {

                            await Filesystem.appendFile({
                                path: `somePath/${fileName}`,
                                data: somefile,
                                directory: FilesystemDirectory.Documents
                            });
                            const finalPhotoUri = await Filesystem.getUri({
                                directory: FilesystemDirectory.Documents,
                                path: `somePath/${fileName}`
                            });
                             

                            if (Capacitor.getPlatform() === 'ios') {
                                this.fileOpener
                                    .open(finalPhotoUri.uri, '')
                                    .then(() => {
                                     this.toastr.toastSuccess(
                                            'File has been downloaded'
                                        );
                                    })
                                    .catch(e => {
                                        this.toastr.toastError(
                                            'An error occurred during the download'
                                        );
                                    });
                            } else {
                                if (finalPhotoUri.uri) {
                                    
                                    this.toastr.toastSuccess(
                                        'File has been downloaded to the Downloads folder'
                                    );
                                }
                            }
                        } catch (e) {

                            this.toastr.toastError(
                                'An error occurred during the download'
                            );
                        }

But I hope there will be another solution form capacitor. Because I do not want to use the Cordova plugin with the capacitor core.

Any idea?

I also wanted to add this approach which only uses the capacitor core.I used a capacitor share plugin.

await Share.share({
                                title: fileName,
                                url: finalPhotoUri.uri, // temporary file path url 

                            });

I hope this trick will help someone

Can you please share full code here?

download(id: number, fileName: string) {
// get necessary file for download
       responseFromAPI().subscribe(
            async (data: Blob) => {
                if (Capacitor.getPlatform() !== 'web') {
                    let dataForDownload: any;
                    const reader = new FileReader();
                    reader.readAsDataURL(data);
                    reader.onloadend = async () => {
                        dataForDownload = reader.result;

                        try {
                            const directory =
                                Capacitor.getPlatform() === 'ios'
                                    ? FilesystemDirectory.Documents
                                    : FilesystemDirectory.ExternalStorage;
                            await Filesystem.requestPermissions();
                            await Filesystem.appendFile({
                                path: `Download/${fileName}`,
                                data: dataForDownload,
                                directory: directory
                            });
                            const finalPhotoUri = await Filesystem.getUri({
                                directory: directory,
                                path: `Download/${fileName}`
                            });

                            if (Capacitor.getPlatform() === 'ios') {
                                Share.share({
                                    title: fileName,
                                    url: finalPhotoUri.uri
                                })
                                    .then(() => {
                                        this.toastr.toastSuccess(
                                            'File has been downloaded'
                                        );
                                    })
                                    .catch(e => {
                                        this.toastr.toastError(
                                            'An error occurred during the download'
                                        );
                                    });
                            } else {
                                if (finalPhotoUri.uri !== '') {
                                    this.toastr.toastSuccess(
                                        'File has been downloaded to the Download folder'
                                    );
                                }
                            }
                        } catch (e) {
                            this.toastr.toastError(
                                'An error occurred during the download'
                            );
                        }
                    };
                } else {
                    this.downloadFromBrowser(data, fileName);
                }
            },
            () => {
                this.toastr.toastError(
                    'An error occurred during the download of the file'
                );
            }
        );
    }

    downloadFromBrowser(blob: Blob, filename: string) {
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');

        a.href = url;
        a.download = filename || 'download';
        const clickHandler = () => {
            setTimeout(() => {
                URL.revokeObjectURL(url);
                removeEventListener('click', clickHandler);
            }, 150);
        };
        a.addEventListener('click', clickHandler, false);
        a.click();

        this.toastr.toastSuccess(
            'File has been downloaded to the Downloads folder.'
        );
    }

WHERE FILEOPENER???

That isn’t a very clear question. Assuming you mean where that is coming from. sj9 said the following:

In any case, Capacitor has first party support now to download files in the filesystem plugin.