Twitter: raymondcamden


Address: Lafayette, LA, USA

Better example of PhoneGap, Parse, and uploading files

07-23-2013 25,914 views Mobile, JavaScript, HTML5 27 Comments

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

Related Blog Entries

27 Comments

  • Commented on 07-25-2013 at 5:21 AM
    hi
    i downloaded and testing on android
    but does not work for me
    have you any idea?
  • Commented on 07-25-2013 at 6:16 AM
    Nope, you have to tell me where it is failing.
  • Commented on 07-31-2013 at 6:34 PM
    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/fyi-fatfractal-makes-fi...

    If you haven't already, check us out!
  • Commented on 08-01-2013 at 7:40 AM
    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.
  • Shaun McCoy #
    Commented on 08-29-2013 at 1: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?
  • Shaun McCoy #
    Commented on 08-29-2013 at 1:53 PM
    Sorry,I use .getPicture to get it from the camera.
  • Commented on 09-02-2013 at 3:50 PM
    Ensure you have the latest release of Parse's JavaScript file.
  • Shaun McCoy #
    Commented on 09-06-2013 at 11:38 AM
    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.
  • Shaun McCoy #
    Commented on 09-06-2013 at 11:41 AM
    It appears that it was the javascript file. Thanks!
  • ticoi #
    Commented on 09-18-2013 at 9:15 PM
    thanks for share.
    i downloaded and test in android but it not work for me.
    what is the variable "parseJSID" ?
    expect your replay.
  • Commented on 09-19-2013 at 9:18 AM
    Parse JS ID comes from your Parse settings. Please see the Parse docs, or my earlier posts, if that doesn't make sense.
  • Matt #
    Commented on 12-11-2013 at 1:38 PM
    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?
  • Commented on 12-11-2013 at 2:41 PM
    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.
  • Thomas Tveten #
    Commented on 01-17-2014 at 9:31 AM
    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);
    }
  • Commented on 01-17-2014 at 9:39 AM
    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.
  • Thomas Tveten #
    Commented on 01-17-2014 at 10:17 AM
    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);
  • Commented on 01-17-2014 at 10:27 AM
    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.
  • Phonegap Geek #
    Commented on 03-04-2014 at 1:26 PM
    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.
  • Commented on 03-05-2014 at 8:20 AM
    Wow, sorry, you got me on this. Maybe post it to the Parse support forum?
  • Phonegap Geek #
    Commented on 03-05-2014 at 8:37 AM
    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?
  • Commented on 03-05-2014 at 8:39 AM
    Sorry, no, I don't.
  • Xmare #
    Commented on 03-08-2014 at 12:07 AM
    Let us say I want to take two picture note, how can I display it in a grid?
  • Commented on 03-08-2014 at 6:56 AM
    Display what? The pictures?
  • Xmare #
    Commented on 03-08-2014 at 8:03 AM
    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
  • Commented on 03-08-2014 at 8:07 AM
    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.
  • Kane #
    Commented on 03-22-2014 at 11:56 PM
    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".
  • Commented on 03-23-2014 at 8:21 AM
    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/how-can-i-give-each-us...

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

Post Reply

Please refrain from posting large blocks of code as a comment. Use Pastebin or Gists instead. Text wrapped in asterisks (*) will be bold and text wrapped in underscores (_) will be italicized.

Leave this field empty