How can I read / write a local json or text in Cordova?

Viewed 2816

I'm trying to read a JSON file inside the www with Cordova.

I'm trying with a lot of systems, tests and solutions which, however, are failing one after the other.

The simplest concept (fetch)

Result: locally work. On app: "fetch fail" if use cordova.file.dataDirectory. The result is: "NetworkError when attempting to fetch resource".

document.addEventListener('click', test ,true);
document.addEventListener('touchstart', test ,true);

function test(){

  alert("try fetch");

  const jsonFile = "./geoita.json"; // fail on app
  const jsonFile = cordova.file.dataDirectory+"geoita.json"; //security

  var headers = new Headers({
      "Content-Type": "text/plain",
      "Accept": "text/plain"
   });
  window.fetch( jsonFile, headers )
  .then( resp => resp.json() )
  .then( json => {

    console.log(json);
    alert("Result: "+json);
    document.body.innerHTML=JSON.stringify(json);


  })
  .catch(function(error) {
    console.log('ERROR: ' + error.message);
    alert('ERROR: ' + error.message);
  });

 };

TEST 2 (request)

Result: security on file. You can't get file, so you can't read it.

  var req = new XMLHttpRequest();
  req.onreadystatechange = () =>
  {
    console.log("try in api style...");
    if (req.readyState==4 && req.status==200)
    {
        console.log("reading...");
        let json = JSON.parse(req.responseText);  
        console.log(json);
    }
    else {
      console.log("try in api style - error: "+req.readyState);
    }
  }

  req.open("GET",(cordova.file.dataDirectory+"geoita.json"),true);
  req.send();

TEST 3 (follow official guideline)

Result: file not found or encoding error.

const filePath = cordova.file.applicationDirectory+"geoita.json"; //return error code 5 ENCODING_ERR
//const filePath = "www/geoita.json"; // return error  1 NOT_FOUND_ERR
//const filePath ="geoita.json"; 
//const filePath = cordova.file.applicationDirectory+"geoita.txt"; // not changed

window.resolveLocalFileSystemURL( filePath ,
fileEntry => {

  console.log("file object ready");

  fileEntry.file(
    File => {

      console.log("content: ",File);

      var reader = new FileReader();
      reader.onloadend = function(evt)
      {
          console.log("read success",evt.target.result);
      };
      reader.readAsText(File);

    });

}, error => console.error(error) );

I'm continuing to do many tests but I don't understand what is going wrong. I don't understand why it is so difficult to read a local file in the www folder of Cordova.

How can I do this?

Update

Over 32 tests after, the solution was wrong.

Unfortunately I just realized that he doesn't write the file from the www folder. What you can do is "create a file from the variable, historicize it in the permanent and then reread it. It still doesn't read as I wanted then.

What happened during the test is simply the result of previous tests where I had "copied the file into the variable by hand", therefore I had unknowingly saved it and was subsequently recovered as if it were reading the local one (which, of course, was identical)

Unfortunately I still can't find a solution to read the file. If you are aware of it, write!

For this example we have 2 type of data.

1) is a simple var within a json message;

2) It's a local file in the directory www (then create a file by hand that you can recover ... a text with false json data similar to the one in 1)

Step 00

add cordova-file-plugin in your project

https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-file/index.html

Step 01

In config.xml

  <platform name="android">
        <preference name="AndroidPersistentFileLocation" value="Compatibility" />
        <preference name="AndroidExtraFilesystems" value="assets,root, files, cache, sdcard, cache-external, files-external" />
    </platform>
    <platform name="ios">
        <preference name="iosPersistentFileLocation" value="Compatibility" />
        <preference name="iosExtraFilesystems" value="assets,root, files,cache, sdcard, cache-external, files-external" />
    </platform>

Potentially you may also need to enter:

<access origin="*" /> <access origin="cdvfile://*"/> <access origin="file:///*"/>

Step 02

Now, after app is ready, example:

  if('cordova' in window)
  {
    document.addEventListener('deviceready', mytest);
    // console.log('device');
  }
  else
  {
    document.addEventListener('DOMContentLoaded', mytest);
    // console.log('web device');
  }

in alternative joint it on event:

  document.addEventListener('click', mytest,true);
  document.addEventListener('touchstart', mytest,true);

Step 03

Into your function of app

  function mytest(){


      // start for read (get the file and pass it on readFile method)
      window.requestFileSystem(LocalFileSystem.PERSISTENT, 0,
      (fs)=> {

        var fileName = "test.txt",
            fileDir =  cordova.file.applicationDirectory.replace( cordova.file.applicationDirectory, ''), //cordova.file.applicationDirectory only get error on entry file
            filePath = fileDir + fileName;

        fs.root.getFile(
          filePath,
          null,
          (fileEntry) => {
            readFile(fileEntry)
          }, fileEntryFail);

      }, filesystemFail);



      // start for write (set a file creator -> get data via var -> pass var on writeFile method)
      window.requestFileSystem(LocalFileSystem.PERSISTENT, 0,
      (fs)=> {

        var fileName = "test.txt",
            fileDir =  cordova.file.applicationDirectory.replace( cordova.file.applicationDirectory, ''), //cordova.file.applicationDirectory only get error on entry file
            filePath = fileDir + fileName;

        fs.root.getFile(
          filePath,
          { create: true, exclusive: false },
          (fileEntry) => {
            writeFile(fileEntry, filetowrite );
          }, fileEntryFail);

      }, filesystemFail);



      // write a file whit data
      function writeFile(fileEntry, dataObj) {
          fileEntry.createWriter(function (fileWriter) {

              fileWriter.onwrite = function() {
                  alert("file write");
                  // readFile(fileEntry); // if you wont read after write it
              };

              fileWriter.onerror = function (e) {
                  alert("Failed file write: " + e.toString());
              };

              fileWriter.write(dataObj);
          });
      }



      // read a file getted 
      function readFile(fileEntry) {

        alert('Reading file....');

        fileEntry.file(function (fileEntry)
        {

            console.log("path to file: ",fileEntry.fullPath);
            console.log("file to read: ",fileEntry.file);

            var reader = new FileReader();
            reader.onloadend = function()
            {
                alert("Successful file read");
                // document.body.innerHTML = ""+this.result;
                console.log("Successful file read: ",this.result);
                // window.dataActive = JSON.parse(this.result);
                // alert('dataActive was read as: ' + dataActive)
                // displayFileData(fileEntry.fullPath + ": " + this.result);
            };
            reader.readAsText(fileEntry);

        }, readFail );

      };


      // fails 
      function filesystemFail (err) { alert("FS: DATA FAIL"); console.log(err.code);};
      function fileEntryFail (err) { alert("File entry: FAIL"); };
      function writeFail (err) { alert("Writing: DATA FAIL"); };
      function readFail (err) { alert("Reading: DATA FAIL"); };


      //data test via var
      var filetowrite =  `{"message": "THIS IS SIMPLE TEST MESSAGE"}`

    };  

Now some important closing notes

It is obviously useless to keep both start read and start write which test on the same file or data. Either write the file and then read it or take it locally and read it directly. So for your tests comment one of the two at a time.

It's also stupid to keep them separate because it would be easy to optimize the system by calling the file system only once.

However, I left them separate to give a clear and functional example to those who, like me, are stuck on logic.

Update

Solution:

While waiting for the whole plugin I am testing to be built:

A) you have to "unlock" access to files in config.xml as I explained above.

B) cordova-file-plugin can only write, read, delete files in the permanent memory inside the app. (I will release a better model than the one above as soon as I can)

C) fetch does not work in any case. BE CAREFUL, it only works on the browser but not in the app

D-1) I am testing the writing and deleting of files in the www. I still need time.

D-2) it is possible to read through an XML request the file in the www in the following way:

var _filename = 'MYFILENAMEINWWW.txt',
    _mimetype = 'text/plain'; // or application/json

    function readfromwww(_mimetype,_filename,callback)
    {


        var request = new XMLHttpRequest();
            request.overrideMimeType(_mimetype ); 
            request.open("GET", _filename );
            request.onreadystatechange = () =>
            {

              if(request.status > 300)
              {
                if(rs==404) console.log('file not found in www: ',request.status);
                else console.log('error on request: ',request.status);
              }

              else if(request.responseText!=undefined && request.responseText!='')
              {

                //from json string to js obj content
                if(_mimetype == "application/json")
                  callback(JSON.parse(request.responseText));

                //return string into your file
                else
                  callback(request.responseText);

              }

            }
            request.send();

          }

    }

          // now you can read a file in www calling the method:

          readfromwww('text/plain', 'test.txt',
            filecontent => {

               console.log('OLE! INTO THE FILE: ',filecontent);

               //not filecontent is the content of your file but... remember... the contents is loaded via ajax, isn't easy to storize it out this function.

            });
2 Answers

You can check out MongOGX which is a front end db inspired by MongoDB where you can save JSON documents and it supports cordova

MongOGX is a simple JavaScript based document store/database inspired by MongoDB. It supports local storage and application data storage (via Cordova - cordova-plugin-file) storage modes. It also supports encryption (via code.google.com/p/crypto-js - added to this repo).

See https://github.com/globules-io/MongOGX

I created a function that returns a promise with a string, then it can be parsed with JSON or whatever you want.

This solution was checked only on Android platform

I also tried to do this with fetch and XMLHttpRequest, but there were no successful results.

Typescript:

export function readFileFromWWW(filename: string) {
  return new Promise<string>((resolve, reject) => {
    getWWWFileEntry(filename, (fileEntry) => {
      ;(fileEntry as FileEntry).file(
        (file) => {
          const reader = new FileReader()

          reader.onloadend = () => {
            resolve(reader.result as string)
          }

          reader.readAsText(file)
        },
        () => {
          reject(new Error('read went wrong'))
        }
      )
    })
  })
}

function getWWWFileEntry(
  filename: string,
  callback: (fileEntry: Entry) => void
) {
  window.resolveLocalFileSystemURL(
    `${window.cordova.file.applicationDirectory}www/${filename}`,
    callback
  )
}
Related