Benjamin D.
Benjamin D.

Reputation: 410

Capacitor FileSystem API - Unable to write a file on device using Filesystem.writeFile

I'm working on a Hybrid app using Angular 9 / Ionic 5 / Capacitor 2.0. I'm trying to save a file (basically PDF or image file) coming from my API as a Blob to the file system of my mobile devices (IOS/Android). My final goal is to reproduce a Browser-like download feature of the file (allowing the user to keep it on his phone).

Here is my code :

  downloadNativeFile(blob: Blob, fileName: string){
    let reader = this.getFileReader();
    reader.onloadend = (readerEvent) => {
      if(reader.error){
        console.log(reader.error);
      } else {
        let base64data: any = readerEvent.target['result'];
        try {
          Filesystem.writeFile({
            path: fileName,
            data: base64data,
            directory: FilesystemDirectory.Data
          }).then((res)=>{
            Filesystem.getUri({
              directory: FilesystemDirectory.Data,
              path: fileName
            }).then((getUriResult) => {
              const path = getUriResult.uri;
              console.log("The file's path : " + path);
              console.log(Capacitor.convertFileSrc(getUriResult.uri));              
            }, (error) => {
              console.log(error);
            });
          }).catch((err)=>{
            console.log("Error", err);
          });
        } catch(e) {
          console.error('Unable to write file', e);
        }
      }
    }

    reader.readAsDataURL(blob);
  }

Executing this code I don't get any error and my console prints the path of the saved file :

The file's path : /DATA/sample.pdf
http://localhost/_capacitor_file_/DATA/sample.pdf

Nevertheless, I can't found any new file on my device (I also tried all values from the FilesystemDirectory enum). Tested on both Android (7 & 10) and IOS (13).

My Blob is good (I can vizualize it in browser) and the dataUrl conversion is properly done.

So it seems that the writeFile function from the Capacitor FileSystem API is silently failing to save the file on the device.

Does anyone know why and how can I fix this ? I'm literally stucked and can't implement this feature at the moment.

Here are my dependencies and plugins :

  "dependencies": {
    "@angular/animations": "^9.1.7",
    "@angular/cdk": "^9.2.3",
    "@angular/common": "^9.1.7",
    "@angular/compiler": "^9.1.7",
    "@angular/core": "^9.1.7",
    "@angular/fire": "^5.4.2",
    "@angular/forms": "^9.1.7",
    "@angular/material": "^9.2.3",
    "@angular/material-moment-adapter": "^9.2.4",
    "@angular/platform-browser": "^9.1.7",
    "@angular/platform-browser-dynamic": "^9.1.7",
    "@angular/platform-server": "^9.1.7",
    "@angular/router": "^9.1.7",
    "@angular/service-worker": "^9.1.7",
    "@auth0/angular-jwt": "^2.1.0",
    "@capacitor/android": "^2.1.0",
    "@capacitor/core": "2.1.0",
    "@capacitor/ios": "^2.1.0",
    "@ionic-native/core": "^5.25.0",
    "@ionic-native/fcm": "^5.26.0",
    "@ionic-native/http": "^5.26.0",
    "@ionic-native/splash-screen": "^5.25.0",
    "@ionic-native/status-bar": "^5.25.0",
    "@ionic/angular": "^5.1.1",
    "@ngrx/effects": "^9.1.2",
    "@ngrx/entity": "^9.1.2",
    "@ngrx/store": "^9.1.2",
    "@ngrx/store-devtools": "^9.1.2",
    "ajv": "^6.9.1",
    "angular2-text-mask": "^9.0.0",
    "animate.css": "^3.7.0",
    "bootstrap-sass": "^3.4.0",
    "capacitor-fcm": "^2.0.0",
    "classlist.js": "^1.1.20150312",
    "cordova-plugin-advanced-http": "^2.4.1",
    "cordova-plugin-file": "^6.0.2",
    "core-js": "^2.6.4",
    "fibers": "^4.0.3",
    "file-saver": "^2.0.2",
    "firebase": "^5.8.2",
    "font-awesome": "^4.7.0",
    "html-webpack-plugin": "^2.21.0",
    "jetifier": "^1.6.6",
    "lottie-web": "^5.4.3",
    "moment": "^2.24.0",
    "ngx-markdown": "^9.0.0",
    "pwa": "^1.9.6",
    "rxjs": "^6.5.5",
    "scrolling-element": "^1.0.2",
    "tslib": "^1.10.0",
    "web-animations-js": "^2.3.2",
    "zone.js": "~0.10.2"
  },
  "devDependencies": {/**/},
  "cordova": {
    "plugins": {
      "cordova-plugin-advanced-http": {}
    }
  }

Upvotes: 0

Views: 16395

Answers (5)

Daniel Rodrigues
Daniel Rodrigues

Reputation: 143

Sharing my code that work OK on iOS and Android devices:

downloadNative(blob: Blob, fileName: string) {
    let reader = new FileReader();
    reader.onloadend = (readerEvent) => {
      if (reader.error) {
        console.log(reader.error);
      } else {
        let base64data: any = readerEvent.target['result'];
        try {
          Filesystem.writeFile({
            path: fileName,
            data: base64data,
            directory: Directory.Documents,
          })
            .then((res) => {
              console.log('res', res);
              Filesystem.getUri({
                directory: Directory.Documents,
                path: fileName,
              }).then(
                (getUriResult) => {
                  const path = getUriResult.uri;
                  console.log("The file's path : " + path);
    console.log(Capacitor.convertFileSrc(getUriResult.uri));
                  this.fileOpener
                    .open(path, 'application/pdf')
                    .then(() => console.log('File is opened'))
                    .catch((e) => console.log('Error opening file', e));
                },
                (error) => {
                  console.log(error);
                }
              );
            })
            .catch((err) => {
              console.log('Error', err);
            });
        } catch (e) {
          console.error('Unable to write file', e);
        }
      }
    };

    reader.readAsDataURL(blob);
  }

Upvotes: 0

Dos Orcino
Dos Orcino

Reputation: 75

Hello sir I having the same problem sir and I want to implement this on my code. sir can I see the complete code for this ?

let reader = this.getFileReader();
reader.onloadend = readerEvent => {
  if (reader.error) {
     console.error(reader.error);
  } else {
     let base64data: any = readerEvent.target['result'];
     let mimeType = base64data.substring(base64data.indexOf(":")+1, base64data.indexOf(";"));
     let options: IWriteOptions = { replace: true };
     this.file.writeFile(this.file.dataDirectory, fileName, blob, options).then(res => {
        this.fileOpener.open(this.file.dataDirectory + fileName, mimeType);
     });
  }
};
reader.readAsDataURL(res.body);

especially this one let reader = this.getFileReader(); and what the imports packages you use thanks

Upvotes: 0

Adam Zabrocki
Adam Zabrocki

Reputation: 359

The output of console.log method can actually give a hint about wrong import of Filesystem.

Instead of:

import { Filesystem } from '@capacitor/core';

Try:

const { Filesystem } = Plugins;

Upvotes: 5

Benjamin D.
Benjamin D.

Reputation: 410

I finally found an other way to do it, so I post it if it can help someone else. To summarize, the purpose was to be able to download / display a file comming from an API as a Blob on a mobile device.

Here is my working code :

let reader = this.getFileReader();
reader.onloadend = readerEvent => {
  if (reader.error) {
     console.error(reader.error);
  } else {
     let base64data: any = readerEvent.target['result'];
     let mimeType = base64data.substring(base64data.indexOf(":")+1, base64data.indexOf(";"));
     let options: IWriteOptions = { replace: true };
     this.file.writeFile(this.file.dataDirectory, fileName, blob, options).then(res => {
        this.fileOpener.open(this.file.dataDirectory + fileName, mimeType);
     });
  }
};
reader.readAsDataURL(res.body);

I finally replaced the usage of FileSystem API by File API. The FileOpener allows to open the file with the appropriate app, depending on its Mime-Type. It works perfectly on all devices I tested (Android 7 & 10 and Ios 13)

Upvotes: -1

Sergey Rudenko
Sergey Rudenko

Reputation: 9235

File system access is tricky since each system (Android, iOS etc) has its own rules:

Android allows the behavior you describe, but instead of FilesystemDirectory.Data as directory to write files you need to use FilesystemDirectory.Documents. See more here: https://capacitorjs.com/docs/apis/filesystem (scroll to FilesystemDirectory enums)

iOS does not allow direct access to files and the way you could try achieve what you want is using icloud folder route. See this answer: Save file to public directory using Cordova FileTransfer

But since your use case is to allow "download" like behavior - try to see if this library would do the trick for you: https://github.com/eligrey/FileSaver.js/ It is web API and not a native API but works fine in modern browsers.

Upvotes: 0

Related Questions