Ionic-native: File.writeFile() refuses to create binary file correctly (png image) ionic 2

Created on 18 Nov 2016  路  9Comments  路  Source: ionic-team/ionic-native

_From @shrike71 on November 18, 2016 10:17_

Short description of the problem:

File.writeFile() creates a PNG file of 0 bytes when trying to write a Blob made from base64 data

screenshot_20161116-151450

What behavior are you expecting?

Base64 data read from a TEXT column in the database is formatted and converted into a Blob, and saved to the application data directory as a PNG image.

Steps to reproduce:

  1. Install recent Ionic 2 / Cordova / NodeJS as illustrated below:
Your system information:

Cordova CLI: 6.3.1
Gulp version:  CLI version 3.9.1
Gulp local:
Ionic Framework Version: 2.0.0-rc.2
Ionic CLI Version: 2.1.1
Ionic App Lib Version: 2.1.1
Ionic App Scripts Version: 0.0.39
OS: Windows 10
Node Version: v6.7.0
  1. Ensure that cordova-plugin-file version 4.3.0 is installed
  2. Examine / Reproduce the code below to observe a 0 bytes file written when trying to write a Blob as an image/png.

Detail

I am trying to create a file that consists of base64 data stored in the db. The rendered equivalent of the data is a small anti-aliased graph curve in black on a transparent background (never more that 300 x 320 pixels) that has previously been created and stored from a canvas element. I have independently verified that the stored base64 data is indeed correct by rendering it at one of various base64 encoders/decoders available online.

The development platform is Windows 10, and i've been testing directly on a Samsung Galaxy S7 and S4 so far.

I know that the base64 data has to be converted into binary data (as a Blob) first, as File does not yet support writing base64 directly in to an image file. I found various techniques with which to do this, and the code which seems to suit my needs the most (and reflects a similar way I would have done it in java is illustrated below):

Main code from constructor:
this.platform.ready().then(() => {
      this.graphDataService.getDataItem(this.job.id).then((data) =>{
        console.log("getpic:");

        let imgWithMeta = data.split(",") 
        // base64 data
        let imgData = imgWithMeta[1].trim();
        // content type
        let imgType = imgWithMeta[0].trim().split(";")[0].split(":")[1];

        console.log("imgData:",imgData);
        console.log("imgMeta:",imgType);
        console.log("aftergetpic:");

        // this.fs is correctly set to cordova.file.externalDataDirectory
        let folderpath = this.fs;
        let filename = "dotd_test.png";

        File.resolveLocalFilesystemUrl(this.fs).then( (dirEntry) => {
            console.log("resolved dir with:", dirEntry);
            this.savebase64AsImageFile(dirEntry.nativeURL,filename,imgData,imgType);
        });
      });

    });
Saves the image with File.writefile():
// save the image with File.writeFile()
savebase64AsImageFile(folderpath,filename,content,contentType){

      // Convert the base64 string in a Blob
      let data:Blob = this.b64toBlob(content,contentType,512);

      console.log("file location attempt is:",folderpath + filename);

      File.writeFile(
        folderpath,
        filename,
        data,
        {replace: true}
      ).then(
        _ => {console.log("write complete:")}
      ).catch(
        err => {
          console.log("file create failed:",err);
        }
      );    
  }

I have tried dozens of different decoding techniques, but the effect is the same. However, if i hard code simple text data into the writeFile() section, like so:

File.writeFile(
        folderpath,
        "test.txt",
        "the quick brown fox jumps over the lazy dog",
        {replace: true}
      )

A text file IS created correctly in the expected application location with the text string above in it. However, I've also noticed that whether the file is the 0 bytes PNG, or the working text file above, in both cases the ".then()" consequence clause of the File Promise never fires.

Helper to convert base64 to Blob:
// convert base64 to Blob
b64toBlob(b64Data, contentType, sliceSize) {

          //console.log("data packet:",b64Data);
          //console.log("content type:",contentType);
          //console.log("slice size:",sliceSize);

          let byteCharacters = atob(b64Data);

          let byteArrays = [];

          for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
              let slice = byteCharacters.slice(offset, offset + sliceSize);

              let byteNumbers = new Array(slice.length);
              for (let i = 0; i < slice.length; i++) {
                  byteNumbers[i] = slice.charCodeAt(i);
              }

              let byteArray = new Uint8Array(byteNumbers);

              byteArrays.push(byteArray);

          }


        console.log("size of bytearray before blobbing:", byteArrays.length);
        console.log("blob content type:", contentType);

        let blob = new Blob(byteArrays, {type: contentType});

        // alternative way WITHOUT chunking the base64 data
        // let blob = new Blob([atob(b64Data)],  {type: contentType});

        return blob;
  }

Additionally, I swapped the above method and used the Ionic 2 native Base64-To-Gallery library to create the images, which worked without a problem. However, having the images in the user's picture gallery or camera roll is not an option for me as I do not wish to risk a user's own pictures while marshalling / packing / transmitting / deleting the data-rendered images. The images should be created and managed as part of the app.

Other information: (e.g. stacktraces, related issues, suggestions how to fix, stackoverflow links, forum links, etc)

User marcus-robinson seems to have experienced a similar issue outlined here, but it was across all file types, and not just binary types as seems to be the case here. Also, the issue seems to have been closed:

https://github.com/driftyco/ionic/issues/5638

Which Ionic Version? Ionic 2.x

_Copied from original issue: driftyco/ionic#9228_

need to reproduce needs reply file

Most helpful comment

@ihadeed, @mhartington

When i write the file with ArrayBuffer instead Blob type. It's working well (Both on Lower Level API and Ionic Native File (File.write)). So my temporary solution here is use the below function to write the file as ArrayBuffer type

const base64ToArrayBuffer = (base64) => {
  const binary_string =  window.atob(base64);
  const len = binary_string.length;
  const bytes = new Uint8Array(len);
  for (let i = 0; i < len; i++) {
    bytes[i] = binary_string.charCodeAt(i);
  }
  return bytes.buffer;
}

When use the Blob type. i debug into the cordova-plugin-file, i see the FileReader onload not callback. So i think the problem from there. But don't know why. Then i test the FileReader outside the plugin. I just read my blob file with FileReader, but it not callback to the onload like cordova-plugin-file. Don't know what happen with the FileReader in Angular 2 or Ionic 2.

I'm sure the Blob file is correct. Because i converted it to UrlObject and assign to html Img tag, the image show correctly

URL.createObjectURL(blob);

All 9 comments

Is this not due to https://github.com/driftyco/ionic-native/issues/789? options.replace is currently inverted.

I got the same problem when try to use low-level API

Your system information:

Cordova CLI: 6.4.0
Gulp version:  CLI version 3.9.1
Gulp local:
Ionic Framework Version: 2.0.0-rc.4
Ionic CLI Version: 2.1.12
Ionic App Lib Version: 2.1.12
Ionic App Scripts Version: 0.0.47
OS: Windows 10
Node Version: v7.2.1
import { Injectable } from '@angular/core';
import { File } from 'ionic-native';

declare const cordova: any;
declare const resolveLocalFileSystemURL: any;
declare const requestFileSystem: any;
declare const LocalFileSystem: any;

@Injectable()
export class StorageService {
  fs: string = cordova.file.dataDirectory;

  writeFile(fileName: string, fileBlob: any) {
    resolveLocalFileSystemURL(this.fs, (dir) => {
      console.log('Access to the directory granted succesfully');
      dir.getFile(fileName, {create: true}, (file) => {
          console.log('File created succesfully.');
          file.createWriter((fileWriter) => {
              console.log('Writing content to file', fileWriter);
              fileWriter.onwriteend = (e) => {
               console.log('Write completed.', e);
              };
              fileWriter.write(fileBlob);
          }, () => {
            alert('Unable to save file in path ' + this.fs);
          });
      });
    });

    // console.log('begin write file', fileBlob);
    // File.writeFile(
    //   this.fs,
    //   fileName,
    //   fileBlob,
    //   {replace: true}
    // ).then(
    //   _ => {console.log("write complete:")}
    // ).catch(
    //   err => {
    //     console.log("file create failed:",err);
    //   }
    // );    
  }
};

@phattranky which Ionic Native version?

@ihadeed the latest version
"ionic-native": "^2.2.13"

But when i try to write a text file, it's working well. Like this

fileWriter.write(['sdasdasdasd']);

I only got the problem when try to write the blob file. I use the method like @mhartington to convert from Canvas Uri to Blob.

Hope my info useful

@ihadeed, @mhartington

When i write the file with ArrayBuffer instead Blob type. It's working well (Both on Lower Level API and Ionic Native File (File.write)). So my temporary solution here is use the below function to write the file as ArrayBuffer type

const base64ToArrayBuffer = (base64) => {
  const binary_string =  window.atob(base64);
  const len = binary_string.length;
  const bytes = new Uint8Array(len);
  for (let i = 0; i < len; i++) {
    bytes[i] = binary_string.charCodeAt(i);
  }
  return bytes.buffer;
}

When use the Blob type. i debug into the cordova-plugin-file, i see the FileReader onload not callback. So i think the problem from there. But don't know why. Then i test the FileReader outside the plugin. I just read my blob file with FileReader, but it not callback to the onload like cordova-plugin-file. Don't know what happen with the FileReader in Angular 2 or Ionic 2.

I'm sure the Blob file is correct. Because i converted it to UrlObject and assign to html Img tag, the image show correctly

URL.createObjectURL(blob);

It works fine for me in iOS (10.2.1) but in Android i have the same problem. Using this solution with ArrayBuffer solved the problem in Android.

I've got the same problem with a pdf file blob. I didn't understand the ArrayBuffer solution, once fileWriter.write or File.writeFile only accepts a string or blob typed parameter. Could someone detail the solution a little more?

I had similar issues writing Blob - the promise will not resolve. Found out that it was due to Angular's zone issue as described here.

The workaround to wrap FileReader did the job for me.

I have got the same problem while writing the image to device(Android) , understood from the above information that writing as blog make the problem , but the fileWriter method will only accept blob and will never accept Array buffer , can any one explain in brief how to write the image captured from camera to a location in the device ...

Was this page helpful?
0 / 5 - 0 ratings

Related issues

GunaSekhar1 picture GunaSekhar1  路  4Comments

ghost picture ghost  路  3Comments

kyleap picture kyleap  路  4Comments

mateo666 picture mateo666  路  3Comments

Manduro picture Manduro  路  3Comments