Better example of PhoneGap, Parse, and uploading files

This post is more than 2 years old.

A few days back I posted about how Parse's JavaScript API now makes it easy to upload files via their SDK. The demo I built was very quick and simple, and while it made use of PhoneGap, it wasn't a great example of the technologies together. Before I spoke on Parse at PhoneGap Day (apparently videos will be posted soon, I'll share when they are) I whipped up a slightly nicer example. Let's take a look.

My example is (I'm sorry) another example of a Note taking app. However this time I've added the ability to attach pictures to a note. The home screen is a listing of your current notes, sorted by date.

Clicking the plus symbol takes you to a form allowing you to write a new note.

At this point you can select to take a picture. Now - for testing purposes in my iOS Simulator, I set the source to the local file system. In a real world app you would ask for the camera itself (or allow the user to select), but I wanted something quick and dirty.

Once you click Save, we then create a new Note object at Parse. The code has to determine if you've taken a picture or not and if you have, it will handle the upload for you. Now let's look at the code.

First - the home page. I'm using jQuery Mobile for the application and have placed both "pages" in the core index.html. Since there seems to be some confusion about this, let me be absolutely clear. jQuery Mobile does not make you use one html page. Period. In a case like this where I have a small app (2 pages), then it made sense for me to include them in one html file. That was 100% a personal choice and not anything jQuery Mobile forced me to do.

The HTML here is pretty bare since almost all of the content is dynamic. Now let's take a look at app.js.

First take a look at the pageshow event for #home. This is where we get data from Parse. This is done via a simple query that orders by object creation. I limit the count to 10 and if I wanted to could add paging.

The addNote logic is a bit more complex. Saving Parse data is asynchronous so if we need to store a file we have two, not one, async calls to make. Hence the big IF block that checks if we've got an existing selected image. To be honest this could be done a bit nicer perhaps. For example, the initial creation of the Note object could definitely be taken out of the IF clause, as well as the line where I set the text property. But in general I think you get the idea.

Anyway, I hope this is useful for folks. I've zipped up a copy of this application and attached it to the blog entry.

Download attached file.

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by tormahiri posted on 7/25/2013 at 2:21 PM

hi
i downloaded and testing on android
but does not work for me
have you any idea?

Comment 2 by Raymond Camden posted on 7/25/2013 at 3:16 PM

Nope, you have to tell me *where* it is failing.

Comment 3 by Dave Wells posted on 8/1/2013 at 3:34 AM

Hi Ray, nice post. Have you tried FatFractal? We put up a blog post, inspired by yours, demonstrating how to do this with our platform:

http://fatfractal.com/prod/...

If you haven't already, check us out!

Comment 4 by Raymond Camden posted on 8/1/2013 at 4:40 PM

Interesting. I like the fact you can do both stores at once. Parse does allow for N storage calls at once, but not for a 'related' thing as we have here.

Comment 5 by Shaun McCoy posted on 8/29/2013 at 10:50 PM

I am getting "Uncaught TypeError : undefined is not a function:181" when setting parseFile = new Parse.File("mypic.jpg", {base64:imagedata});

Any ideas?

Also, how would I go about adding the option to get the image from the camera instead of the gallery?

Comment 6 by Shaun McCoy posted on 8/29/2013 at 10:53 PM

Sorry,I use .getPicture to get it from the camera.

Comment 7 by Raymond Camden posted on 9/3/2013 at 12:50 AM

Ensure you have the latest release of Parse's JavaScript file.

Comment 8 by Shaun McCoy posted on 9/6/2013 at 8:38 PM

I am still getting the "Uncaught TypeError: undefined is not a function when setting parseFile = new Parse.File("mypic.jpg", {base64: imagedata});

Here is a pastebin: http://pastebin.com/AxmLECbU

Any help would be greatly appreciated.

Comment 9 by Shaun McCoy posted on 9/6/2013 at 8:41 PM

It appears that it was the javascript file. Thanks!

Comment 10 by ticoi posted on 9/19/2013 at 6:15 AM

thanks for share.
i downloaded and test in android but it not work for me.
what is the variable "parseJSID" ?
expect your replay.

Comment 11 by Raymond Camden posted on 9/19/2013 at 6:18 PM

Parse JS ID comes from *your* Parse settings. Please see the Parse docs, or my earlier posts, if that doesn't make sense.

Comment 12 by Matt posted on 12/12/2013 at 12:38 AM

Hey Raymond, great article - One of the problems I frequently run into though with phonegap is uploading photos with the base64 data causes the app to crash from memory issues, do you know how you could upload just using the file?

Comment 13 by Raymond Camden posted on 12/12/2013 at 1:41 AM

Well, you have a FileTransfer API in PhoneGap that works perfectly for binary. But you can't use that with the Parse Upload API. You *could* obviously use it as a 'mix' - so you upload the file to some S3 location, or some other server, get the URL, and use the URL in the Parse object.

You may also want to try different values for Quality as well as Target Height/Width. By default those mobile pics are freaking huge and you probably don't need them that big.

Comment 14 by Thomas Tveten posted on 1/17/2014 at 8:31 PM

Hi,

I tried it out, and it worked perfectly, thanks! At the beginning I tried to do what the phonegap.docs recommend, by using uploadPhoto, instead of gotPic. Im sorry for all the code below.

navigator.camera.getPicture(uploadPhoto,function(message) { alert('get picture failed'); },{ quality: 50, destinationType: navigator.camera.DestinationType.FILE_URI,sourceType: navigator.camera.PictureSourceType.PHOTOLIBRARY }

And then get base64 by using FileReader:

function readDataUrl(file) {
var reader = new FileReader();
reader.onloadend = function(evt) {
base = evt.target.result;
};
reader.readAsDataURL(file);
}

However, this gave me a base64 string, but an empty picture. I get the same problem with mediafiles, and wondering if you could help me out. I get a base64 string, but when I upload it to parse, the audio file is empty. I want to get a file, by using its name, like I do below:

function gotFS(fileSystem) {
fileSystem.root.getFile('rec.mp3', {create: false}, gotFileEntry, fail);
}

function gotFileEntry(fileEntry) {
fileEntry.file(gotFile, fail);
}

function gotFile(file){
readDataUrl(file);
}

function readDataUrl(file) {
var reader = new FileReader();
reader.onloadend = function(evt) {
base = evt.target.result;
};
reader.readAsDataURL(file);
}

Comment 15 by Raymond Camden posted on 1/17/2014 at 8:39 PM

Hmm. I'd remove Parse from the picture. Try adding the base64 string to the DOM as an image. I believe you would do that by setting the SRC equal to data:image/png;base64,X where X is the base64 data.

Comment 16 by Thomas Tveten posted on 1/17/2014 at 9:17 PM

Like this?

var file = new Parse.File("sound.x", {base64:base}, "image/png");

If so, it didn't work. I believe I get the base64 of an empty file, because that happened with the image I tried to upload when I used the readDataUrl to get the base64 string. Is any other methods I can use to get the file's base64 than the methods above? To start the gotFile method above, Im using window.requestFileSystem(LocalFileSystem.PERSISTENT, 100*1024, gotFS, fail);

Comment 17 by Raymond Camden posted on 1/17/2014 at 9:27 PM

No, what I meant was - you are having an issue with the base64 becoming a 'real' file on Parse. My suggestion was to NOT upload it (for now) and test it by writing the string out into the DOM for testing purposes.

Comment 18 by Phonegap Geek posted on 3/5/2014 at 12:26 AM

Hi,

I'm struggling to get the file upload to work on ios and android when Im using cordova. Something strange with Audio recording... Im building an application using cordova, and using the media object to record the audio for both Android and iOS. I record the media file as a .wav file (recording.wav) for both platforms. When Im finished with the recording, I retrieve the base64 encoded string with the file system and readDataUrl (base= evt.target.result). The base64 encoded string looks good for both devices, its just one big difference and that is ios have a much larger base64encoded string than Android.

I play the recorded media to be sure that everything is good, which it is and upload the base64 encoded string to parse.com as a file. Before I upload it, I remove the first letters in the base64 which is on Android: data:audio/x-wav;base64, and on iOS: data:audio/wav;base64, . When I try to play the sound I recorded on android, on a android device it works perfectly. The same for iphone, When I try to play the sound I recorded on Iphone on a android device, every thing works fine. However, when I try to play the sound I recorded on the android phone on a iphone, it does not work!

I try to play the media file in parse.com, and on iOS it works fine, but on Android it looks empty.. and I cannot play it from the web, just on a android phone using my application and retrieving the url from parse.com.

I play the media file just by retrieving the url from the file object from parse.com. I guess this is something due to the fact that android record the wav file on a different way? If I look on the base64 from the wav file in android, it is as I mentioned much smaller, and complete random characters. However, on iOS its a LOT of "A"s in the beginning of the base64 string. Am I suppose to add lots of A's to the base64 before I send it up as a file to parse.com?

Please help if you have any knowledge of this.

Comment 19 by Raymond Camden posted on 3/5/2014 at 7:20 PM

Wow, sorry, you got me on this. Maybe post it to the Parse support forum?

Comment 20 by Phonegap Geek posted on 3/5/2014 at 7:37 PM

I did, still no answer. I just got one quick question if you know the answer to it. I have now altered the media plugin to change the bitrate and output format from default to this

this.recorder.setAudioSamplingRate(44100);
this.recorder.setAudioEncodingBitRate(96000);
this.recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
this.recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);

I read a place that this was necessary to do, to be able to play it on ios. I have tried the extensions m4v, m4a, mp4 and mp3 but none of them work on the ios.

On ios I get the error "Failed to initialize AVAudioPlayer: The operation couldn’t be completed. (OSStatus error 2003334207.)" and then the error code 4 which means that the audio is not supported. Any idea why this might be?

Comment 21 by Raymond Camden posted on 3/5/2014 at 7:39 PM

Sorry, no, I don't.

Comment 22 by Xmare posted on 3/8/2014 at 11:07 AM

Let us say I want to take two picture note, how can I display it in a grid?

Comment 23 by Raymond Camden posted on 3/8/2014 at 5:56 PM

Display what? The pictures?

Comment 24 by Xmare posted on 3/8/2014 at 7:03 PM

Hi Raymon, thanks for your quick respond.
I would like to show the picture in a grid view as an attachment, so later in the process all the attached files could be saved.
Like

AttachButton

Grid view of Attached Files

Attachement 1 ...... ... Picture1
Attachement 2 ...... ... Picture2
Attachement 3 ...... ... Audio file

Thanks

Comment 25 by Raymond Camden posted on 3/8/2014 at 7:07 PM

At a high level, the code would need to remember a list of selected files and render them in a list.

Um... I guess that's not terribly helpful. But you get the idea. So I'd begin by modifying my code to allow for multiple clicks. My imagedata variable would need to become an array that you append to.

Comment 26 by Kane posted on 3/23/2014 at 8:56 AM

Mr Camden,

I've been thinking of the best way to implement something like this for a while and am curious on your thoughts. My question: when using these cloud storage services, what is the best way to identify by the phone and not by user to easily retrieve data from the db. I don't need my users to login I just want them to get the data associated with their previous app sessions. So far I've thought maybe the best way is to use the phone UUID as the "key".

Comment 27 by Raymond Camden posted on 3/23/2014 at 5:21 PM

The issue with that is - I believe - iOS prevents you from getting the phone UUID. Actually I'm wrong - from the docs:

The uuid on iOS is not unique to a device, but varies for each application, for each installation. It changes if you delete and re-install the app, and possibly also when you upgrade iOS, or even upgrade the app per version (apparent in iOS 5.1). The uuid is not a reliable value.

I think the crucial part though is that is "not reliable." Check out this post from Parse:

https://parse.com/questions...

From what I see there - it sounds like you can use an anonymous user to get a unique ID.

Comment 28 by karthick posted on 11/13/2014 at 11:55 AM

Hi Mr Camden,
I am using cordova for file upload and recording in ios. I recorded one audio and very next movement i am try to upload that audio and it successfully uploaded.But i record one audio and this file automatically saved in "voice memo" in ios and after few hours later i want to upload that audio, i cant fetch the file from voice memo n ios. The directory shows only for photo and video files not for audio.how to fetch voice memo files for upload?? please help me to figure out.

Comment 29 by Raymond Camden posted on 11/13/2014 at 8:07 PM

Sorry, not sure about that one. I'd recommend posting a bit more details about the issue to Stack Overflow.

Comment 30 by Ashima Bansal posted on 11/28/2014 at 10:23 AM

How to get text files, or pdf files from phone. It only works for images?

Comment 31 (In reply to #30) by Raymond Camden posted on 11/28/2014 at 2:56 PM

Well it depends. My example uses base64. You could use Cordova's File API to read in a file and convert it to base64.

Looking at the JS API now for this, https://parse.com/docs/js/s..., it looks as if you could pass a FIle object, which should be possible w/ Cordova. So give it a shot.

Comment 32 by fanstein posted on 12/19/2014 at 6:26 AM

Please please send me files please, the link is broken... really i need testing

Comment 33 (In reply to #32) by fanstein posted on 12/19/2014 at 6:26 AM

fanstein@hotmail.com

Comment 34 (In reply to #32) by Raymond Camden posted on 12/19/2014 at 11:09 AM

Link is fixed.

Comment 35 by Yousuf posted on 6/23/2015 at 10:11 PM

How to make MySQL?

Comment 36 (In reply to #35) by Raymond Camden posted on 6/24/2015 at 2:51 AM

How to make what?