Tuesday, May 3, 2011

iOS and Application Cache

Background
As I have mentioned before, I started immersing myself in HTML 5 in order to provide a good, cross-platform mobile solution for a product that I have been working with. A critical selling point for this tool is the capability for it to work offline after downloading all the needed content once and the true targeted device for this is the first generation iPad. While developing to target the iPad, it would have been silly to write a web app and not make it cross-platform compatible with devices like the increasingly popular Android platform or really anything that supports the subset of HTML5 features I am using.

In writing applications that are cross-platform, there are always nuances that you must be aware of. For instance, my application uses but does not rely solely upon touch gestures because not all platforms (past BlackBerry models and such) that are mobile also support touchscreen interfaces.Another example, and one that I am not currently implementing, is geolocation whereas not all devices have that available (or will authorize its use).

Application Cache
Almost nothing could attract me to use the HTML5 web application as the development path over native applications except for the implementation of application cache. This little subset allows for the offline storage of your entire web application (html, images, javascript, etc). To implement it is really simple, too, which is what really attracted me to it.

Because there are already some great resources out there for implementing this, I will simply provide some links to those resources that I most appreciate here:
Dive Into HTML5 - Great resource, but caters more to Apache web server
The CSS Ninja - Another great resource including steps for IIS web server

Platform Nuances
My application, in its simplest state, operates as an image viewer. The images, however, are very "busy" and large in size especially when bundled together in albums. This wouldn't normally matter except that application cache is inconsistently implemented with regard to limits on storage. For example (and only accurate as of this writing) Safari on the Mac will store whatever you throw at it. Mobile Safari (on iOS devices), however, prompt you to increase your storage space at regular megabyte intervals (5, 10, 25, etc). Android devices let you store whatever you throw at it. Internet Explorer on the PC doesn't support application cache even as of IE 9. Chrome on the PC supports up to 5 MB of storage but nothing above that; attempting to store above that causes nothing at all to store (per the spec). The good news is that you can test for all of these cases, but the targets are ever-shifting. No big surprise there, but it is certainly worth noting that these are the perils you face when going cross-platform.

Recently I noticed a specific behavior on iOS devices when I finally surpassed the 5 MB initial threshold for the app. When the pop up window asked to increase storage, I presumed (incorrectly) that the system would be smart enough to expand the storage and then stuff the content in there. If you ask me, that seems like no real leap of faith to believe that. However, that action currently triggers an error on the application cache javascript object and does not store any of what has already been downloaded in the cache. That means that you have to handle the error (not really an error at all), make sure that it is an "OK" error based on the device type, and then force a refresh of the page to re-initiate the caching.

The good news: it is all doable and has been handled in my application easily and peace in the (current) mobile world has been maintained. If anyone should have interest in the code or more specific examples, feel free to comment or email and I will be happy to provide them.

6 comments:

Andy said...

Hi Matthew,

I'm interested in your fix for the iOS cache limit increase error reported here in your blob post.

I'm seeing the same issue with a mobile application I'm developing: after authorizing the cache limit increase, the application cache JS object triggers an error. I am properly catching the error event but have not yet forced an automatic page refresh. If I refresh the page manually, the cache object immediately throws the error again and no cache download occurs.

Any advice you could share on what you did to get around this? I'm running on iPhone w/ iOS 4.2.7 (Verizon).

Thanks!

Andy Riedel
ariedel@kabam.com

Matthew Ray said...

Interesting... It is possible that there is something more to it with the manifest file. Chrome has some great tools built into it (as does FireFox I believe) for watching the manifest file download. The best thing I did was to get Chrome on the desktop to accept it (even though it will ultimately reject it if above 5 MB) just to validate my files. THEN I test on iPhone/iPad/etc.

Here is a couple of snippets I use in my appplication and I hope this gives you some ideas:

//Here I am making some determinations about the nature of the device itself

if ((navigator.userAgent.match(/iPhone/i)) || (navigator.userAgent.match(/iPod/i)) || (navigator.userAgent.match(/iPad/i))) {
//Using iDevice
isIDEVICE = true;
}
else {
isIDEVICE = false;
}

online = (navigator.onLine) ? 'yes' : 'no';

//that is useful for this next piece

function handleCacheError(e) {
if (!!window.navigator.standalone) {
//alert('cache error occurred in standalone mode. May need to restart this web app.');
}
else {
}
if (online == 'yes' && isIDEVICE == false) {
//Device may not support application cache or have some weird requirement.
//This would be where I would force them to use it online only
}
if (online == 'yes' && isIDEVICE == true) {
//Refresh page to make sure we get all content loaded into cache
alert('Reloading to complete offline storage.');
location.reload(true);
}
}

Paperfish said...

I'm almost in the exact situation as you, creating a small app for a few users on iPad 2's, that requires offline image storage. And I'm a web developer, and I really don't want to invest the time and money (wow, lot's of money as I'm learning) in making a native iOS app. Just for something that HTML and Javascript (not even HTML5!) can handle nicely.

So I'm reading your 5MB limit stories... Have there been any more recent revelations? Does iPad 2 give you the option to increase the size at 5MB increments? Is the 5MB a "total" Safari limit? Or is it 5MB per URL or per hostname?

Thanks again for posting all that you have learned.

Matthew Ray said...

Paperfish:

Definitely just glad to help out however I can. I love the iPad and even now am typing this using a bluetooth keyboard (Logitech) on my iPad 2.

The 5 MB limit is there, sort of. Lets assume an iPad (or iPhone or iPod Touch) where you havent had any apps using applicationCache as a strage mechanism. When the iDevice (generic term) has downloaded 5 MB of content for offline storage it will stop, throw a javascript error, and prompt the user to expand the storage. Saying "ok" does NOT store what has already been downloaded as far as I can tell, but instead expects you to re-download the content. I do this in the code above just by catching the javascript error and throwig a message to the user saying that cache has to be refreshed and automatically refreshing the page when they say "ok" to my message. This restarts the donwloading based on the manifest file FROM THE BEGINNING. Not cool.

What I would like to see is WebKit support for a more robust messaging with the applicationCache object so that better decisions can be made. For instance, right now my application's handler for the cache error that simply means iOS wants to increase storage the same as other generic unhandled errors that fit similar criteria. This has led to some users of mine seeing a nonstop cycle that appears to be the device always needing more space for cache but really being another cache error that, simply put, can not be diagnosed with ease.

I am more than willing to share my more recent pieces of code if need be, but hope that people like you will join me in asking Apple's Mobile Safari folks to get involved in this.

Paperfish said...

Thanks for your quick feedback!

What about the case of wrapping the web app in a UIWebView, and accessing the HTML and images locally (maybe storing them locally a priori, maybe accessing them for the first time over HTTP but then storing them locally for subsequent use) and packaging the whole thing as a native app? Do the same limits apply, or can you cache up to the app sandbox limit?

I really hate to go down this road (for one thing it would mean I'd have to buy a Mac to do the app development), but in the case of this app the images won't be updated all that often.

Again, thanks for your response!

Matthew Ray said...

Paperfish:

I recommend taking a look at PhoneGap.com before getting too far. I am just stating to play with it (expirementing) but it has a lot of promise early on. The essence of it is to take your app and, as you pointed out, wrap it and create native applications based on the HTML + javascript code. My early test was an astounding sucess and there is plenty of community support as well as documentation from the PhoneGap group itself.

The bad part, as you also noted, is that you have to use the native machine type to compile still. This means operating on a Mac for such a basic thing. I have one here at work so that wasn't a limiter for me, but I dont at home and can definitely recognize this as a barrier.

Interesting to note: Android doesn't seem to experience any of this behavior and handles caching invisibly (doesn't ask to increase limit, just caches it).

Anyway, if I find a better approach to any of these, I will be sure to post back here and let you all know. Please do the same for me! :-)

About Me

My photo

Firecracker, father, friend. Honorable, humorist, heathen. Performer, programmer, producer. Seeking, social, sarcastic. Loyal, logical, lasting. Bold, belying, benevolent. Gamer, genuine, grinning. Reader, redeeming, ridiculous.

On and off blogger, film producer, and programmer. Keurig addict. Frequent moviegoer. 

Voice Comments