Uploading images using XHR2, Cordova and Android ICS

I ran into a problem recently in an App I was writing for both desktop and mobile use.  The image capture and more specifically the upload component which worked fine on desktop was not working on android.

There were several issues I had to overcome to get this working:-

  • Cordova File object is not based on Blob.
  • Android ICS XHR2 does not support sending blobs
  • Didn’t want to change our existing client-server protocol, which expected binary images to be posted to the server.

The first task was to get a Blob from a Cordova File object.  Cordova’s FileReader object however does not support  readFileAsArrayBuffer() only readFileAsDataURL() and readFileAsText().

Using readFileAsDataURL() I parse the URL and extract the data, decoding it if necessary using window.atob() so I end up with raw image data (this code I found on the internet and is widely referenced not sure what the original source was):-

/**
 * Convert a dataURL to raw data
 * @method dataURLToRaw
 * @param dataURL {string} Data URL (of the form data:...)
 * @returns {object} Raw data object { data }
 **/
 dataURLToRaw: function(dataURL) {
   var BASE64_MARKER = ';base64,', parts, contentType, raw;
   if (dataURL.indexOf(BASE64_MARKER) == -1) {
     parts = dataURL.split(',');
     contentType = parts[0].split(':')[1];
     raw = parts[1];
   } else {
     parts = dataURL.split(BASE64_MARKER);
     contentType = parts[0].split(':')[1];
     raw = window.atob(parts[1]);
   }
   return { data: raw, type: contentType == "null" || contentType == "" ? null : contentType };
 },

Having done that, and because XHR2 on ICS does not support sending a Blob object, I need to use an arraybuffer which it does support, so to convert the raw data to an array buffer, I use

/**
 * Convert raw data to a Uint8Array
 * @method rawToArray
 * @param raw {object} Raw data object
 * @returns Uint8Array
 **/
 rawToArray: function(raw) {
   var l = raw.length, a = new Uint8Array(l), blob;
   for (var i = 0; i < l; ++i) {
    a[i] = raw.charCodeAt(i);
   }
   return a;
 }

Then combining the two, and Cordova’s FileReader object, I ended up with the following code:-

 function send(body, contentType) {
   var XHR = new XMLHttpRequest();
   XHR.contentType = contentType;
   XHR.open("POST", "/your/server/uri");
   XHR.onreadystatechange = function(e) {
     // ...
   }
   XHR.send(body);
 }
 // If running under cordova or dont have upload capabilities (so not XHR2) then
 // upload as array buffer
 if (isCordova || !(new XMLHttpRequest).upload) {
   var reader = new FileReader();
   reader.onloadend = function(e) {
     var a = rawToArray(dataURLToRaw(reader.result).data).buffer;
     send (a, "arraybuffer");
   };
   reader.readAsDataURL(data);
 } else {
   send (data, contentType);
 }

 

Advertisements

About austinfrance

Technical Developer @ RedSky IT / Explorer Software
This entry was posted in Android, Cordova, html5, JavaScript, upload. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s