Capacitor writeFile - Saving pdf file is in invalid format

I will try that tomorrow too. To be honest I don’t even know how to find that file path on an android phone manually. I tried and failed. My personal phone is an iphone haha.

1 Like

Well putting the MIME type prefix on didn’t help.
It works fine (without prefix) if I cut and paste the string and covert to pdf file using this:
http://base64converter.com/

It turns out that it is just writing it as a text file. I should have checked for that ages ago. I guess this means the capacitor file system can’t write a pdf with a base64 string.
Or maybe I need to change the encoding from UTF8 to something else?

I don’t think so, didn’t checked deeply but seems that Capacitor “just” create a File from what you provide, so if you provide a pdf path it should create a pdf file (see https://github.com/ionic-team/capacitor/blob/781d2307fe92f535c1ec2a692ac6c0f1dcbe924f/android/capacitor/src/main/java/com/getcapacitor/plugin/Filesystem.java#L212)

What’s the value of the path you are providing to Capacitor writeFile? Does it contains the extension “.pdf”?

2 Likes

You sir are a genius! I would buy you a beer if you were in my timezone :wink: Great idea to look at the code.
I got it working.
I just needed to remove the UTF8 encoding.

So…
If you look at the docs here (https://capacitor.ionicframework.com/docs/apis/filesystem/#type-401)
It says that the encoding defaults to UTF8, so I didn’t think removing it would make any difference.

However looking at the code in the link you sent it defaults to plain text if you provide a value in encoding.

Charset charset = this.getEncoding(encoding);
...
 // if charset is not null assume its a plain text file the user wants to save
3 Likes

Awesome, glad to hear you found the solution!

That’s well noted, you never know, I might travel one of these days :wink:

Happy coding, have a great Sunday

Hello,
I still have the same problem.
After saving a pdf and opening the same it displays the invalid formed message to the PDF

This is my code for creating the PDF file:
`const { Filesystem } = Plugins;

console.log("compartilhar: ", Filesystem);
try {
  Filesystem.writeFile({
    path: 'wsp.pdf',
    data: "PDF capacitor test",
    directory: FilesystemDirectory.Documents
  }).then((res) => {
    console.log("success pdf: ", res);
  });
} catch(e) {
 console.error('Unable to write file: ', e);
}`

Can someone help me?
Thanks in advance!

If you are using the below (and it is not just a placeholder) you are just sending text as data. Not a pdf.

  1. If you don’t specify an encoding type it assumes it is a base64 string.
  2. You are sending raw text. Not a base64 string. Are you using a pdf generator like pdfMake?

See my code below:

downloadPdf(pdfBase64: string) {
    const { Filesystem } = Plugins;

    if (this.plt.is('cordova')) {
        // Save the PDF to the device
        const fileName = 'defectreport.pdf';
        try {
          Filesystem.writeFile({
            path: fileName,
            data: pdfBase64,
            directory: FilesystemDirectory.Documents
            // encoding: FilesystemEncoding.UTF8
          }).then((writeFileResult) => {
            Filesystem.getUri({
                directory: FilesystemDirectory.Documents,
                path: fileName
            }).then((getUriResult) => {
                const path = getUriResult.uri;
                this.fileOpener.open(path, 'application/pdf')
                .then(() => console.log('File is opened'))
                .catch(error => console.log('Error openening file', error));
            }, (error) => {
                console.log(error);
            });
          });
        } catch (error) {
          console.error('Unable to write file', error);
        }
      } else {
      // On a browser simply use download
      this.defectReportService.getPdfObj().download();
    }
  }
1 Like

Hello @wekas
I’m trying to create a file with PDF format.
Regardless of the content of the PDF being a base64 or simply a text I would have to be able to open the same one, however I can not open any PDF file

This is my complete code:

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

import { FileOpener } from '@ionic-native/file-opener/ngx';

async createFile(base64: string) {
    const { Filesystem } = Plugins;
    try {
      const fileName = "wsp.pdf";
      Filesystem.writeFile({
        path: fileName,
        data: base64,
        directory: FilesystemDirectory.Documents
      }).then((res) => {
        console.log("Success create file: ", res);
        Filesystem.getUri({
            directory: FilesystemDirectory.Documents,
            path: fileName
        }).then((getUriResult) => {
            const path = getUriResult.uri;
            this.fileOpener.open(path, 'application/pdf')
            .then(() => console.log('Success open'))
            .catch(error => console.log('Error open File: ', error));
        }, (error) => {
            console.log(error);
        });
      });
    } catch(e) {
     console.error('Error create File: ', e);
    }
  }

Thanks for listening!

Hi Luca,

I know its not what you want but if you use this it should work for text:

Filesystem.writeFile({
            path: 'doc.txt',
            data: 'I am a line of text',
            directory: FilesystemDirectory.Documents
            encoding: FilesystemEncoding.UTF8  //Any item here assumes it is text
          })

As for the pdf here is what I would try:

  1. MIME type:
    Do you have a MIME type on your Base64 data? You may need it for the pdf app on the device to recognise it.
    I add it in my code:
this.myService.getPdfObj().getBase64((data) => {
        const pdfBase64 = 'data:application/pdf;base64,' + data;
        this.downloadPdf(pdfBase64);
      });
  1. Try writing you base64 string to the console and copy and paste it into an online pdf generator like this:
    http://base64converter.com/

  2. Find the file on your device and email to computer then open it in a browser (different app). If it doesn’t work try changing the extension to .txt to make sure it is not text.

Let me know if any of that works.

Les adjunto mi solución
import { Plugins, FilesystemDirectory } from ‘@capacitor/core’;
const { Filesystem } = Plugins;

let base64Code = pdfAsString;
try {
const result = await Filesystem.writeFile({
path: ‘carnet.pdf’,
data: base64Code,
directory: FilesystemDirectory.Documents,
})
console.log(‘Wrote file’, result);
} catch(e) {
console.error(‘Unable to write file’, e);
}

This is my solution with capacitor v3:

    downloadFile(urlFile: string): void {
        this.http.get(urlFile, {
            responseType: 'blob',
            reportProgress: true,
            observe: 'events'
        }).subscribe(async (event) => {
            if (event.type === HttpEventType.DownloadProgress) {
                this.downloadProgress = Math.round((100 * event.loaded) / event.total);
            } else if (event.type === HttpEventType.Response) {
                this.downloadProgress = 0;
                const name = urlFile.substr(urlFile.lastIndexOf('/') + 1);
                const base64 = await this.convertBlodToBase64(event.body) as string;
                const savedFile = await Filesystem.writeFile({
                    path: name,
                    data: base64,
                    directory: Directory.Documents,
                });
            }
        });
    }
convertBlodToBase64 = (blob: Blob) => new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.onerror = reject;
        reader.onload = () => {
            console.log('convBase64', reader.result);
            resolve(reader.result);
        };
        reader.readAsDataURL(blob);
    });

The solution for the FILE_NOTCREATED error, you should add to the <application> in AndroidManifest:

android:requestLegacyExternalStorage="true"

The solution for the error: package android.support.v4.content does not exist, try this:

//replace
public class FileProvider extends android.support.v4.content.FileProvider
//for this
public class FileProvide extends androidx.core.content.FileProvider

Man, you saved my day. Spent so much time analyzing where it was going wrong.Though this does seem unexpected behavior. There should be better documentation.

This works fine:

    // If content is Blob, convert it to Text
    let convertBlob;
    if (content instanceof Blob) {
        convertBlob = await content.text();
    }

    if (isPlatform('ios')) {
        await Filesystem.writeFile({
            path: filename,
            data: convertBlob ? convertBlob : content,
            directory: Directory.Cache,
            encoding: Encoding.UTF8,
        });

This helped me as well, thank you guys!

It’s incredible how a simple thing can be so difficult and time taking to find out how to deal with, so, to help others I’ll explain in details here my problem so others may find it helpful.

I wanted to load a PDF file from my local assets folder to capacitor FileOpener plugin but I found that I couldn’t just take the file out of the assets folder because capacitor can’t read those files as they are internal on the webview.

The solution I found to open the PDF from my local assets folder was to use some http client to download the file as a base64, then use capacitor Filesystem plugin to write a temporary file and send it to FileOpener plugin.

I used angular HttpClient. It’s important to note that you can’t add the Encoding to filesystem.

Here is the code:

    public async openFile() {
        const filepath: string = `assets/images/books/my-book.pdf`;
        this.openLocalFileOnFileOpener(filepath);
    }

    async openLocalFileOnFileOpener(filepath: string, contentType: string = 'application/pdf') {
        return this.httpClient.get(filepath, { responseType: 'arraybuffer' }).subscribe(async (response) => {
            //
            // Get base64 data
            const binaryData = new Uint8Array(response);

            let binary = '';
            const bytes = new Uint8Array(binaryData);
            const len = bytes.byteLength;
            for (let i = 0; i < len; i++) {
                binary += String.fromCharCode(bytes[i]);
            }

            const base64Data = window.btoa(binary);

            //
            // Create temporary file
            const { uri } = await Filesystem.writeFile({
                path: 'temp1.pdf',
                data: base64Data,
                directory: Directory.Cache,
            });

            //
            // Open file
            FileOpener.open({ filePath: uri, contentType: contentType });
        });
    }

sir please! i beg you! i’m having the same issue and i’m tired, i’ve tried to implement your code, but it seems you have a variable called fileOpener and i have no idea where it comes from (i’m a beginner developer) could you please explain to me how to implement it correctly?

try {
      Filesystem.writeFile({
        path: fileName,
        data: pdfBase64,
        directory: Directory.Documents
        // encoding: Encoding.UTF8
      }).then((writeFileResult) => {
        Filesystem.getUri({
            directory: Directory.Documents,
            path: fileName
        }).then((getUriResult) => {
            const path = getUriResult.uri;
            this.fileOpener.open(path, 'application/pdf')
            .then(() => console.log('File is opened'))
            .catch(error => console.log('Error openening file', error));
        }, (error) => {
            console.log(error);
        });
      });
    } catch (error) {
      console.error('Unable to write file', error);
    }

line 13. Thanks! (Sorry if there are spelling errors, English is not my native language.)

I still have the same error, it tells me that the file is damaged when opening the excel with the android application. This only happens when I save it with writeFile, because if I transfer the file that is in s3 to the phone and open it originally, it opens correctly

import { FileOpener } from '@capacitor-community/file-opener';
import { Directory, Filesystem,Encoding,   } from '@capacitor/filesystem';

interface CustomFile {
  title: string,
  url: string,
  mineType: string
}
const openLink = async (file: CustomFile) => {
  try {
    const response = await fetch(file.url);
    const blob = await response.blob();
    const base64data = await convertBlobToBase64(blob)
    
    const savedFile = await Filesystem.writeFile({
        path: file.title,
        data: base64data,
        directory: Directory.Documents,
        // encoding: Encoding.UTF8,
        // recursive: true
      });
       await FileOpener.open({
         filePath: savedFile.uri,
         contentType: file.mineType,
       })
 
  } catch (error) {
    console.error(error);
  }
};


const convertBlobToBase64 = (blob: Blob) : Promise<string> => new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onerror = reject;
    reader.onload = () => {
        resolve(reader.result as string)
    }
    reader.readAsDataURL(blob);
})

openLink({
  title: 'gestion_0HV9WdBxQ1.xlsx',
  url: 'https://laguagua-bucket-br.s3.sa-east-1.amazonaws.com/informes/items/gestion_0HV9WdBxQ1.xlsx',
  mineType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
});

Excel App Mobile message:

We have encountered a problem with some of the content of “gestion_0HV9WdBxQ1.xlsx”. Do you want us to try to recover as much as possible? If you trust the origin of this book, click yes

and something strange happens, that file is saved in my documents obviously on my android phone. What I do to test that file is search for it and then I try to send it to myself by WhatsApp and WhatsApp tells me “the selected file was not a document.” To test another case, I located that file written by FilesystemwriteFile and took it to my computer to open it with Microsoft Excel Online and when I try to open it it throws an error

{
“mistake”: {
“code”: 400,
“message”: “Conversion of the uploaded content to the requested output type is not supported.”,
“errors”: [
{
“message”: “Conversion of the uploaded content to the requested output type is not supported.”,
“domain”: “global”,
“reason”: “conversionUnsupportedConversionPath”
}
]
}
}

Same problem. I am trying to write pdf file on android…

my code is:

        const url = "https://pdfobject.com/pdf/sample.pdf"
        const fileName = "test.pdf"
		try {
			const response = await fetch(url);
			const blob = await response.blob();

			const base64: string = await new Promise((resolve, reject) => {
				const reader = new FileReader();
				reader.onerror = reject;
				reader.onabort = reject;
				reader.onload = () => resolve(reader.result as string);
				reader.readAsDataURL(blob);
			});

			await Filesystem.writeFile({
				path: fileName,
				data: base64,
				directory: Directory.Documents,
			});

			Toast.show({ text: `ok ${fileName}` });
			Haptics.impact({ style: ImpactStyle.Light });
		} catch (e) {
			Toast.show({ text: `${e.toString()}` });
		}

Versions:

    "@capacitor/android": "^5.7.0",
    "@capacitor/app": "^5.0.7",
    "@capacitor/core": "^5.7.0",
    "@capacitor/filesystem": "^5.2.1",
    "@capacitor/haptics": "^5.0.7",
    "@capacitor/ios": "^5.7.0",
    "@capacitor/keyboard": "^5.0.8",
    "@capacitor/splash-screen": "^5.0.7",
    "@capacitor/status-bar": "^5.0.7",
    "@capacitor/toast": "^5.0.7",
1 Like

I have a similar implementation. I have the issue that PDF files are empty - the page number is correct, but there is no content. Image files are also invalid. Have you found any solution?

It works if I use Filesystem’s downloadFile method to download a file, but there are places where I cannot use that and I have the Blob from a “traditional” http request. I don’t know if it’s a problem with the http request on Android or the file writing / encoding / whatever.

May be you can update now.

npm install @capacitor-community/file-opener
npm install capacitor-blob-writer

From Stackoverflow: ionic framework - Capacitor download and open file from api - Stack Overflow
Capacitor Filsesystem: Filesystem Capacitor Plugin API | Capacitor Documentation
capacitor blob writer: GitHub - diachedelic/capacitor-blob-writer: Capacitor plugin to write binary data to the filesystem

Finally i did find out with Ionic Capacitor with only and pdfmake. Write it as base64 and remove the // encoding: Encoding.UTF8
It will automatically save as base64.

  async convertToTxt(textString: string)  {
    const docDef = {
      content: [
        { text: textString }
      ]
    };
    const fileName = `ImgToText_${Date.now()}.pdf`;
  
    if (Capacitor.isNativePlatform()) {
      pdfMake.createPdf(docDef).getBase64(async (data) => {
        try {
          const result = await Filesystem.writeFile({
            path: fileName,
            data: data, // Utilisation de la valeur Base64 obtenue
            directory: Directory.Documents,
            // encoding: Encoding.UTF8
          });
  
          if (result) {
            console.log('PDF écrit avec succès sur le système de fichiers:', result.uri);
            alert('PDF écrit avec succès sur le système de fichiers:\n' + result.uri);
            // Ajoutez ici le code pour ouvrir le fichier PDF si nécessaire
          } else {
            console.error('Erreur lors de l\'écriture du fichier PDF sur le système de fichiers.');
            alert('Erreur lors de lécriture du fichier PDF sur le système de fichiers.');
          }
        } catch (error) {
          console.error('Erreur lors de lécriture du fichier PDF:', error);
          alert('Erreur lors de lécriture du fichier PDF sur le système de fichiers.');
        }
      });
    } else {
      // Télécharger le PDF dans le navigateur
      // const pdfBlob = await pdfMake.createPdf(docDef).getBlob();
      pdfMake.createPdf(docDef).download(fileName);
    }
  }