As you know, lately I've been publishing simple Cordova examples that involve the file system. I'm working on a demo that involves background asset downloads (see the blog entry) but I thought I'd take a break from that and write up something super simple, but hopefully helpful, that demonstrates file writing.
With that in mind I built a demo that writes to a log file. The idea being that your app may want to record what it is doing. Normally you would do that via XHR to a server, but logging to a file ensures it will work offline as well. And perhaps you don't really need the data on your server but just want a log you can check later if things go wrong. Let's take a look at the code bit by bit.
The first thing I need to do is get a handle to the file. I'm going to use a file inside cordova.file.dataDirectory, which is an alias to an application-specific folder with read/write access.
window.resolveLocalFileSystemURL(cordova.file.dataDirectory, function(dir) {
console.log("got main dir",dir);
dir.getFile("log.txt", {create:true}, function(file) {
console.log("got the file", file);
logOb = file;
writeLog("App started");
});
});
resolveLocalFileSystemURL
converts the alias path into a directory entry object. That has a variety of methods but the one we care about is getFile
. Note the create:true
flag. This ensures that the file will be created the first time it is run. I also copy the file object into a variable logOb that is global to my application. Finally I call writeLog
. That's my utility function. Let's look at that next.
function writeLog(str) {
if(!logOb) return;
var log = str + " [" + (new Date()) + "]\n";
console.log("going to log "+log);
logOb.createWriter(function(fileWriter) {
fileWriter.seek(fileWriter.length);
var blob = new Blob([log], {type:'text/plain'});
fileWriter.write(blob);
console.log("ok, in theory i worked");
}, fail);
}
So first off, if logOb wasn't created, we simply return. My thinking here is that log file writing is not required for the application. I want to silently ignore if we couldn't write to the file system for whatever reason. I modify the input a bit (adding a timestamp and a newline) and then begin the write operation. This particular code was taken right from the HTML5Rocks article on the FileSystem API. It uses seek
to append as opposed to overwrite the file.
With this in place I could then add calls to the log utility from my application. Since my "application" is just a demo, I added two buttons that do nothing but log.
document.querySelector("#actionOne").addEventListener("touchend", function(e) {
//Ok, normal stuff for actionOne here
//
//Now log it
writeLog("actionOne fired");
}, false);
document.querySelector("#actionTwo").addEventListener("touchend", function(e) {
//Ok, normal stuff for actionTwo here
//
//Now log it
writeLog("actionTwo fired");
}, false);
The final thing I did was add a special function that would read the file out and send it to console. I did this just for testing.
function justForTesting() {
logOb.file(function(file) {
var reader = new FileReader();
reader.onloadend = function(e) {
console.log(this.result);
};
reader.readAsText(file);
}, fail);
}
To see this in action, I used GapDebug and my iOS simulator. That let me run justForTesting
right from my browser. In the screen shot below, note that the beginning of the file isn't formatted right. That's because I forgot the newline initially.
I hope this helps. You can find the complete source in my GitHub repo with the rest of my demos: https://github.com/cfjedimaster/Cordova-Examples/tree/master/writelog
Archived Comments
Hello Raymond,
do you have telepathic abilities? Sometimes it's scary how I think about a problem in my cordova project and 30 minutes later there is a blog post on your page about it :-D
Happend several times already.
Thanks a lot for all your interesting and helpful articles.
Glad to help. :)
You are the man Raymond :)
Hi Raymond,
I'm trying to create a file from a blob in Cordova, but want it so be visible to the user as a doawnloaded file would be. How do I achieve that?
Thanks,
Chris
What do you mean, "visble to the user as a downloaded file would be"? In iOS, for example, users don't have a file system they can really use. In Android I believe there is a Downloads view. Can you explain more about what you mean?
In my Cordova app I have a download link for a file created from a blob(example: a.jpg). When the user clicks on the link I want the file to download to the device. It should work the same as if you download an attachment from your mail client. Hope that makes sense...
Well, I don't know what the path is for 'generic' downloads in Android, but the file plugin would let you write anywhere honestly. I'd maybe suggest Googling for the Android download path and see if you can write to it.
Using Cordova to write an app on the iPhone and I have a directory of JSON files. If I dynamically create a JSON file and use file writer can I then overwrite the JSON file at he original location or will it save elsewhere?
You need to see if you have Write access to the directory. The docs for the File plugin list directories and what kind of access you have.
Do you have any advice about retrieving local video files on a device and then sending them via xhr?
Did you try the FileSystem plugin?
Yep, ended up doing that in the end. Cheers :)
Everybody loves Raymond! Lifesaving post.
Glad you found it useful. :)
Thank you! How do I access the .txt file to confirm that it was created?
Use the File plugin to read it.
Hello Raymond!
I have been using your example to save a text file in the dataDirectory and worked great both in Android and iOs, but now I am having trouble in some android devices when it comes to create the Blob object the app crashes.
What could be happening?
Thanks a lot for your advice
I don't know - that's a bit OT. I'd suggest posting to the Google group to get a broader audience. :)
I am running your code and got it working with no error on the android. Can you tell me how to copy the .txt file to my computer? I connect my android tablet to my mac via a usb cable and running the android file transfer tool. I can see other files on the tablet but can not see the .txt file. Advice?
It could be a memory issue. If you are reading a large binary ob into memory and there isn't enough, the app is going to crash.
Could be a memory issue but also happened with a simple "hello".
I test my software on many different devices and only happened in some android devices and never under iOS.
Maybe it could be due to operanting system versions.
I don't know but I solved getting rid of the Blob object and writing directly the text.
var s = 'hello';
fileWriter.write(s);
I don't know if it is a good practice but, so far, it works fine on all devices I tested.
Again, I thank you for your cooperation.
Hi,
Thanks for the great tutorial. Although I can write to the file correctly, if I try reading from it as soon as the page loads, I get an error saying logOb is undefined. How can I make the read method wait until logOb is available?
Are you waiting for deviceready?
Hello Raymond,
I change your code. I added this:
writeLog("actionOne fired");
writeLog("actionOne again fired");
log.txt shows "actionOne again fired". "actionOne fired" is not there.
Any clues?
best regards,
Hans
Can you create a demo I can run here to verify? I don't have this www anymore.
how about here:
https://github.com/cfjedima...
Oh well duh. ;) I'll check it out tomorrow.
Hello Raymond,
Did you have time for checking this?
Thanks,
Hans
Not yet.
Hi Raymond,
I used your files as is, built with Cordova CLI for Android. It all seems to work but no file is created in the file system. Using the Chrome debugger did not reveal anything obvious, console logs the file and where it is stored, I simply cant find a file there.Any ideas as to why this file isn't being created?
Does the justForTesting func work for you?
Should have tried that before. It seems all is functioning correctly, I just can't find the file. How do I access file:///data/data from my computer?
For both Android and iOS you can find programs that let you explore the file system on a device.
Is there no directory that this plugin can save to for access from windows file explorer? If not I'll find an alternate explorer.
Thanks for the help.
Your device can't write to your machine. That would be a horrible security violation. :)
Sorry, I'm not being very clear. The only way I can tell that the file is being created is because of the justForTesting func. I can not find log.txt on my phone using Android My Files nor when exploring the phones file system with Windows File Explorer.
All I want is log.txt on my computer and I can find it. The directory Phone\Android\data\com.app.writelog\files is empty and a search reveals nothing.
Oh, probably because datadirectory is: " Persistent and private data storage within the
application's sandbox" So you can't get it to externally. If you want to you'll need to use another folder.
Okay, thanks very much. For some reason, my assumption was that persistent meant it would persist after termination of the app :) Should have Googled it.
It does persist - but it is sandboxed.
Got it working with externalDataDirectory. My first mobile app ever, thanks for the help!
good day, chris and raymond. i am currently experiencing this very same issue and i have not been able to find a solution for it. i made a web form that gathers data then writes it (via blob) to a user defined text file ("default.txt", for example), available for download via a generated link on the web page (a "Save default.txt" link). it works fine on the web page but not after it is packaged into a final .apk. has a solution for this been found? thanks in advance!
I'm not sure I understand. You send the data from the Cordova app to a server where it is saved to a web page?
hi raymond! i was trying to get javascript code similar to this
http://blog.eliacontini.inf...
to work in a cordova-packaged web form.
the data is user-generated (just fills up a form) and just needs to be saved on the device (no server required). and maybe opened using a notepad app in the future. i hope that is clear. thank you again for the quick reply!
That article doesn't make sense in a Cordova context. You don't need to download the data - it already exists on the device. Instead you need to simply write it to the file system or persist it some other way, ie via LocalStorage, WebSQL, or IndexedDB.
hello again, raymond!
sorry to be a total noob (which i am)... i went over the localstorage documentation and am beginning to understand how that "doesn't make sense in a Cordova context".
the example on
https://cordova.apache.org/...
seems spot on for my needs. i just can't bridge the writing to a .txt file part (a text file that can later be opened using a separate notepad app).
can you spare me the insight on how to go about "simply writing" the data to the file system?
thank you for your patience! i am definitely bookmarking your blog! :)
So wait - you want to open the file later with another app? Can i ask why? Typically apps can write to a part of the file system only they can access.
hi, raymond! sorry for the late reply.
"...you want to open the file later with another app?"
yes, and maybe even on a desktop running notepad or gedit.
"...can i ask why?"
it is a little hard to explain, but here goes. i am a healthcare volunteer to an outreach program in a fringe area in southeast asia. while the main base camp has internet and cellphone capabilities (poor signal, though), the main field has NO connectivity whatsoever. so i developed a web form for the team so we can still use our mobile phones to gather data for disease incidence and prevalance and later upload the data for statistical purposes etc. i have tried several survey authoring tools available on the internet like formstack, even ones that allow offline data collection and then later upload the data once within connectivity range - and that worked for a while. we soon realized that aside from gathering data, we needed to intervene in these areas as well (since it would literally be months before we could get back to these people again). and an algorithm for health intervention based on each individual's needs was beyond the scope of a simple survey tool. now since i had dabbled in html and js a while back, i was able to develop a webform but it would not run the same in every mobile phone. this got me into apache cordova and here we are.
sorry to pour all of this info onto you. i know it is consuming a lot of blog space. but i have been trying to figure this out for almost a month and i just can't quite get it. i feel it is staring me in the face but can't seem to put two and two together.
thanks for your time
To be clear - you are talking about saving data on a mobile device and then opening it via Notepad (or another editor) on a desktop device. While *possible*, it is not really something you do.
My read on what you want to do is just store the data and then upload it later. So you could write it to a file on the mobile device, but you don't want to edit it with Notepad, you want the mobile device to simply send it to the server when it eventually gets wifi again.
For that I'd probably *not* use the file system, but instead look at the WebSQL API or the SQLite plugin. It lets you use a small database on the mobile device. You could store data there when offline, and when online, begin sending the data there.
It isn't a trivial task, but that's where I'd start.
hello, file created successful but idk where it saved ?!
where is the directory in my phone !!
If you examine the file object (used in the handlers above), you can get the full path. It may even be .fullPath. Just console.dir it.
i use external Dir and it saved on data folder
thanks for replay you are my hero in Cordova issues
can this code help me to write the blob object in pdf format and avail that generated pdf for download
Not exactly. Take a look at PDF.js for dynamic client-side PDF generation.
I have tried using the cordova plugins from CLI like
inappbrowser
file
fileTransfer
fileOpener
but thats not helping me out to generate that view pdf
Sorry - I can't help more here. I haven't used PDF.js myself yet - I just know of it.
How to overwrite the file instead of append?
Usefull infos: intercept event of write ok or write fail: See similar example here https://developer.mozilla.o..., at "Basic Concepts" section
From what I see here, http://www.html5rocks.com/e..., if you don't seek to the end, you end up overwriting. You may want to delete the file and then write.
I ended explicitly deleting the file, than writing, to be sure. It works..
I have a requirement like this link
http://www.techumber.com/20...
for cordova android application
As I am new to cordova and its file system I dnt know how to use them
Then your best bet is to begin by reading the docs and trying some examples.
Tried multiple thing but sadly none of them helped :(
Hello Raymond,
thanks for the article. I have a problem that my application recreates the file every time I turn it on. How should I solve it?
Thanks!
Are you rebuilding the app every time, or literally just rerunning it?
It was a false alarm :) Something else was the issue. Thanks for getting back to me.
Thank you for this – it helped me! FYI, it sounds like this is an issue with pre-Lollipop Android: http://community.phonegap.c...
I commented further down in the thread, but I thought you may also want to know: it looks like this is an issue with pre-Lollipop Android http://community.phonegap.c...
Btw, thanks for this extremely helpful post! I would never have gotten file i/o working with Cordova without your help.
Hi Raymond,
I have CSV formatted string, I am trying to create .csv file which contains that CSV formatted String and save on a device, so I can access and can also email from my device. I tried your example, but with cordova.file.dataDirectory, I didn't see any file created on my android device. But when I used cordova.file.externalDataDirectory, it creates log.txt on my device under <app_id>/data/file/log.txt, but it is blank. Doesn't write anything on it.
Also I want to develop for all the platforms, if I use externalDataDirectory it will only work for android device.
Using Cordova for the first time. Looking forward for you guidance!!
Sincerely,
Vrushank
Not sure what to say - according to the docs, dataDirectory should be writeable. Best I can suggest is to create a reproduceable case where you write to it and try to read and then file it as an issue against the File plugin.
Thanks for the prompt response.
Issue is I want to export device embedded SQLite data to .csv file. I already made one function which get all the data from the database table and coverts into CSV format. Now I want to create .csv file on a device where I can write that CSV formatted string and can use it to email or print from the device.
Looking forward for your guidance.
Thanks again,
Vrushank
But I told you what to do, right? As you said, you were able to write the CSV but your unable to read it. So do as I suggested: Make a simple example of this, don't even worry about CSV, just write "hello world" to the new file. Confirm you can't read it. Then file a bug against the plugin and include the code.
It works when I comment the line
//var blob = new Blob([log], {type:'text/plain'});
and Just passed simple string fileWriter.write('Hello World'); I can see now when I opened that file on a my android device.
I don't understand the reason why I am having the problem to work with Blob object.
When you use Chrome Remote Debugging, do you see an error?
Very useful example! I found the scoping of variables tricky to follow in this example so I rewrote it into a single function.
https://gist.github.com/rjs...
Thats a nice one there. The whole File API is really screaming for a simpler to use wrapper.
Need a an easy to use wrapper for File API? Try Cordova Promise FS https://github.com/markmari...
Found that thanks to searching on Bower. Here's the example of writing a file with that API. https://github.com/markmari...
Nice - thanks for sharing that!
Did you get this to work? I am having the same issue on Android. Works fine in IOS
hi Raymond, we are developing an hybrid app where we have a chat module where we need to store data in mobile(local storage) and need to send to backend
we are facing a problem how to store images, text and voice record (example like whatsapp ) and if he needs we need to load all is older data including images ,voice record
Thanks for a suggestion or help
Saying you are facing a problem is not the same as saying what your problem is. ;) I need to know what you tried and what went wrong before I can offer any advice.
we tried with window.localStorage to store text. but i don't know how to store with images and voice records.
You can convert binary data to Base64 and store it in LocalStorage, but it will be quite large. I'd use the file system.
Hello Raymond, your articles have been very useful. I have one issue though. When I call the writeLog method multiple times, it doesn't write all the lines. It is not consistent which lines it writes. So for example:
writeLog("1");
writeLog("2");
writeLog("3");
The above code doesn't write all three lines. I think this is because of async nature of the API, can you suggest how can I achieve this? Thanks a lot.
Are you saying it doesn't write all 3 or it writes them out of order?
It doesn't write all three lines. Mostly it writes the last line.
Yes. I confirm. I even used your exact code and it prints only the last line.
Confirmed - and it kinda makes sense too. Since the call is async, you have issues where N calls at once try to append to the same file.
The easy fix is to simply make writeLog async - ie, make it fire a callback when done or use a promise. Then the client code (I mean the code using writeLog) has to be sure *not* to fire a write event until the previous call was fired.
That's not ideal, but it would solve the issue. I'll do some thinking on this to see if I can come up with something nicer.
Hi Raymond, thanks for the nice article. However, you might want to mention that, for those of us who do not have the cordova file plugin installed out of the box, doing
cordova plugin add cordova-plugin-file
cordova plugin add org.apache.cordova.file-transfer
in your project folder first will get you considerably further;-)
I got a couple of messages while installing the plugins, but so far everything looks good. Is there maybe a more recent version of this plugin?
Here my installation messages:
compileDebugJavaWithJavacNote: Some input files use or override a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
Note: Some input files use unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
Yeah - I try to call out the plugins I'm using in a demo but I definitely forgot this time - thanks.
To your question - you mean beyond what you get with a normal plugin install? I don't know - you would need to check the repo to see if there's something not released yet. If you're worried about those messages, I'd file a bug report.
Sir, dir.getFile("log.txt", {create:true}, function(file) is creating a new file, but i want to create a file inside the folder like MyDocs/log.txt , so for creating folder where i should write the code plz help me...
Check the link to the HTML5Rocks article and they discuss how to create a folder if it doesn't exist.
Hello Raymond, could you please share an example of how to make the writeLog method sync?
I've got a mental model of how I'd do it - but I haven't actually written it yet. Keep in mind that it *is* logging time stamps, so the data is correct, just not in perfect order. You could still use it for debugging.
Hi,
did you solve this problem with async write ?
Hello,
do you have any solution for async append to file ?
Not yet.
Hello sir, i tried using your code as a test for cordova file creation but it didn't really work , and i still get : "Uncaught TypeError: Cannot read property 'dataDirectory' of undefined " on the line : "window.resolveLocalFileSystemURL(cordova.file.dataDirectory, function(dir) " is there anything i am missing ?
i did install both plugins ,file and filetransfer and i am using Intel XDK latest version.
Did you wait for deviceReady to fire? That's the only reason I can think for cordova.file to be undefined *if* you did install the plugin.
good article but and where it is that the file is created because I do not see it anywhere run program from eclipse and i can see the file anywhere
I explain this in the post. I use this shortcut, cordova.file.dataDirectory, which is a R/W folder for the app. You can print out the full path if you want using a console statement.
Hello Raymond,
I was wondering if it's possible to use a plugin's functionality from another plugin. For example, if I'm developing a plugin that requires writing and reading to a file, can I utilize the functionality already implemented in cordova-plugin-file or do I need to implement it (or include the code from the file plugin) directly in my plugin?
I've searched online but I can't find a good reference for it. Thanks!
Wow... um.... honestly I have no idea. My gut says no, but I'm not sure. I'd consider posting this to StackOverflow (as it doesn't necessarily pertain to this blog entry anyway ;). Let me know when you do, I'd like to follow it.
Thanks for you reply. I've already posted it but haven't gotten any reply. Not even a comment. Haha.
https://stackoverflow.com/q...
From other somewhat related posts I've found, it seemed possible but in a very roundabout way.
Raymond Camden Exactly the case with me. I missed to enclose the db init in the deviceReady or rather $ionicPlatform.ready in my case. Thank you :)
Hi,
In mobile device where the log file saved. i search but i can't able to view
It is saved to ordova.file.dataDirectory,which is an alias defined by the File plugin. Check the docs for that and you will see what it means for each platform.
hey raymond,
thanks for the blog,
i want to encrypt the html5 websql database
could you please help in this case.
Thanks for the time & Appreciate you help.
There are a few ways you can do encryption in JS, but it really isn't my area of expertise. I'm not sure there is any 100% safe way of doing it either. Sorry - I can't help you here.
This may help: https://github.com/litehelp...
Great Post! Learnt a lot from this!
I'm curious.... Im new to Javascript but not to other programming languages. Is it bad practice to have to create a new instance of "File Reader" every time you want to output. Would it work to save a cache version of that FileReader and reference that.
The same with when you go to WriteLog, would saving the "fileWriter" you get in the success to a variable initially and then reuse that each time you want to write the log? Is it actually create a new instance here everytime you write to the log? surely this is bad for overhead? Help me under better thank you
I believe, and I'm maybe 80% sure, you need a new File Reader for each file so it contains the right information.
For writeLog, you could save the writer object I believe.
Hey my file is not getting written... the all console work except this "ok, in theory i worked"
My file is not getting written.. all console are shown except this "ok, in theory i worked"
So no errors at all?
Hi.
It's possible save a html file and open the same?
I've try using toUrl() method but not works. The url is something like ... /persistent/... Not found error
Honestly don't know - there may be permissions errors trying to load HTML from that directory. I'd suggest reading in the HTML and just injecting it into a DOM element.
This option works, but with some problems to load embeded scripts.
You can maybe try opening a new window and pointing to it.
Hi.
I expressed myself poorly, save and open the file works, what does not work is the file being accessible by a URL.
There is a toUrl () method, which serves the file of a "persistent" folder, but it seems that this file can not be made available as a "web content", eg http://.../page1.html or http://.../johndoepic.jpg.
I'm sorry - you got me here. I haven't done much Cordova in a while (moving strictly to PWAs for web apps) so I'm a bit rusty.
Thank you for the article, I was having a fit trying to do this just following the file documentation, very grateful!