0

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": {}
    }
  }
Benjamin D.
  • 400
  • 1
  • 5
  • 15
  • HI Ben, when you are saying "I can't find file on device" - can you explain how are you checking that? Basically native file APIs allow you to work with sandboxed filesystem and would be good to clarify how you are trying to retrieve the file? – Sergey Rudenko Jul 06 '20 at 13:06
  • Hi Sergey, I'm trying to get it from my FileExplorer. For exemple, the Documents directory is supposed to be the public Documents folder, isn't it ? I also tried to open the file using the FileOpener API. I got a "File not found" error – Benjamin D. Jul 06 '20 at 13:20
  • Actually the URI returned by the writeFile function looks weird : /DATA/sample.pdf don't you think ? – Benjamin D. Jul 06 '20 at 13:21
  • Try using: Capacitor.convertFileSrc(getUriResult.uri) and add result to your question. Also if you can please add clarification which filesystem you test on (Android?) – Sergey Rudenko Jul 06 '20 at 13:24
  • I just edited the post to add what you asked. I tested on Android 7 & 10, IOS 13. The path i get from Capacitor.convertFileSrc(getUriResult.uri) is http://localhost/_capacitor_file_/DATA/sample.pdf – Benjamin D. Jul 06 '20 at 13:35
  • cool so it actually means write works fine. Its the ability to access the file from external app etc is an issue. – Sergey Rudenko Jul 06 '20 at 13:37
  • Actually I tried on Android to use the FilesystemDirectory.Documents folder. But the result is exactly the same. I can't basically open the file using the FileOpener API and still get a "File not found" error – Benjamin D. Jul 06 '20 at 13:47
  • if the file path looks like `/DATA/sample.pdf`, it means you are using the web version of the filesystem plugin, that's not a native path. How are you importing/using Filesystem class? – jcesarmobile Jul 06 '20 at 14:57
  • How did you transform the blob so that it is writeable with capacitor? It seems the capacitor api requires a string, cant we just use the blob? – Stefan Jul 07 '20 at 08:11
  • No @Stefan that's why I converted the Blob to a dataUrl (base64 string). – Benjamin D. Jul 07 '20 at 09:17
  • @BenjaminD. Are you fixed this. Actaully I am trying to download PDF from remote URL. Please share your findings – Padmanathan J Jan 11 '21 at 06:12

5 Answers5

5

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;
Adam Zabrocki
  • 351
  • 2
  • 8
  • You saved my day! I didn't understand why Filesystem was not working on real devices. – TheBosti Aug 31 '20 at 21:26
  • Thank you so much ! I had a broken service after a refactor but it's almost impossible to spot because it doesn't throw any error but instead fallback to the browser indexeddb API ... – benuuts Nov 22 '21 at 15:07
  • @Adam Zabrocki it's returning file 0 when reading text file. – Abhay Singh Feb 17 '22 at 06:22
0

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.

Sergey Rudenko
  • 8,809
  • 2
  • 24
  • 51
0

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

Dos Orcino
  • 75
  • 2
  • 8
0

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);
  }
-1

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)

Benjamin D.
  • 400
  • 1
  • 5
  • 15
  • 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 ? especially this one ``` let reader = this.getFileReader(); ``` and what the imports packages you use thanks – Dos Orcino Nov 04 '20 at 15:43