Leveraging iPads for Data Collection
Our latest post is from special guest and Engine Yard partner Jimmy Thrasher of Terralien. Jimmy works with clients to help them build the technology for their businesses. Terralien has been building Rails applications for clients since 2006, and was super excited when Engine Yard came online as one of the first Rails-focused managed hosting providers. Having a strong technology partner made it easier early on to convince clients to use this new technology called “Ruby on Rails.” As someone who’s been using dynamic languages since before they were “cool”, Jimmy’s primary focus these days is crafting full stack mobile+web experiences. He lives in and works from Efland, NC, excels at esoteric analogies, and always tries to have something cooking on the side to feed his entrepreneurial aspirations.
Imagine this scenario
You’ve just won a contract to build a form heavy app for a medical practice with reams of paper forms to convert. There’s a patient evaluation form, a treatment form, an insurer form and a dizzying array of other information they need to gather.
They know the iPad will give them instant credibility with their patients. And they are expecting you to be the magic working developer who can make it happen.
Getting started is easy but if this was you, you’d hit a wall really fast. You’d be faced with a sticky ‘trilemma’:
- You could hand code each form - but that will take forever
- You could create the forms in Interface Builder – but that will make changes really difficult and it will still take forever
- You could implement a form layout framework for iOS – but that won't be portable and will take forever too
Any time something “takes forever”, it’s bad for the client’s budget and that’s bad for you.
Instead of thinking “if only I could use HTML forms” and figuring out how to get out of your newly won deal, you should consider a fourth idea that might not be as obvious.
A new idea
Mobile and web - two technologies that were made for each other like peanut butter and chocolate. They both have strengths but there’s an underlying weakness in each too:
- iOS is great at enabling simple and usable apps. Try doing an app heavy on data entry, though, and you'll face some steep technical hurdles.
- Web apps are extremely flexible, portable and make form building a cinch. That said, getting the kind of flair and snappiness a client wants can be a challenge.
What’s a magic working developer to do?
In this post, I’ll discuss a technique I’m using at Terralien to construct a hybrid application – one built to make the iPad an excellent data entry device without losing its sleek, simple interface. While I’ll be focusing on iOS, the techniques used here should be portable across other smartphone platforms – that’s actually a big part of the appeal with this approach.
General Approach
The approach I use is fairly simple: use a web app for forms, and a combination of the native app and a RESTfulAPI for the rest. The iOS app is effectively partitioned along the two UI lines of implementation, native UI interaction and form rendering and interaction.
Structure of the iOS App
There are three primary components involved in getting this working in an iOS app: ObjectiveResource, a special UIWebView category I call PlatformIntegration, and some corresponding Javascript on the web site to enable the platform integration. Of course, there are the actual HTML forms, and native UI for the iOS app, but those are covered many other places and we won’t go in to them here.
ObjectiveResource
The role ObjectiveResource plays is to provide the native app with access to the relevant application data. How ObjectiveResource works is outside the scope of this post, but I’d encourage you to read up on it yourself: ObjectiveResource.
UIWebView+PlatformIntegration
I’ll explain more in a bit, but the purpose of this piece is to provide interaction with the content in the UIWebView, e.g. calling code on the page, receiving events, and the like.
For code, see: UIWebView+PlatformIntegration snippet___ __
This is the content/server side of the equation. It provides some simple hooks to observe form changes, submit the form, etc.
For code, see: platform_integration.js snippet
Interacting with the Forms
Since the native app + ObjectiveResource/REST API should be fairly straightforward, I’ll focus on explaining the form interactions. In all my travails, I’ve only needed two things: to be able to execute code on the webpage, and to be able to receive events from the webpage.
Executing Code on the Page
This is fairly simple. In fact, UIWebView already provides stringByEvaluatingJavascriptString: and Android’s WebView can be beaten into submission by calling loadData with a ‘javascript:’ URI. Most of the work, then, is involved in making the code into a usable API. For example, it’s much nicer to say, for instance,
[webView hideElementWithId:@"ye_form"];
than to say
[webView stringByEvaluatingJavascriptString:@"$('ye_form').hide()"];
Making these simpler facade methods gets me most of the way there, but for code that is more involved, I use the platform_integration.js code I mentioned. It’s not much more than a namespaced set of functions that simplify the client side work.
Receiving Events
My client code needs to know when the form is changed, so I can add a helpful ‘*’ next to the title, and so I can prompt for confirmation should the user choose to close the form. This is a little more tricky, perhaps even kludgey, but the fundamental technique is reliable and commonly used.
The core of the technique centers around the fact that a UIWebViewDelegate is given the responsibility of determining whether a given URL should be processed as is (see UIWebViewDelegate’s webView:shouldStartLoadWithRequest:navigationType:). Our web page can emit URIs of various kinds, some ‘http:’ and some ‘platformintegration:’. If our code sees a ‘platformintegration:’ URI, it processes it itself, and leaves all other URIs to load normally. Let’s look at some details.
If you take a look at the platform_integration.js snippet, you’ll notice the following line:
window.location = 'platformintegration:formchanged';
This is our event. When the form change event happens, we execute that line, our UIWebView hackery receives the event, and processes it accordingly. In my case, I call:
if ([delegate respondsToSelector:@selector(webViewFormChanged:)]) [delegate webViewFormChanged:self];
Closing, Limitations
You may’ve noticed that all this depends on being online the whole time. For an iPad user in an office, this is a reasonable expectation, but for a field iPad user (our friend the DA ‘enforcer’), the implementation needs to handle sketchy or nonexistent data connections. I’ve not solved this problem, since I haven’t needed to, but I imagine something combining HTML5 offline storage and a simple data caching layer may be able to do the trick. This, though, is a problem for another day.
Aside from that, it provides a relatively simple, sane, and extensible bridge between your native app and the forms on your web app. Each platform does what it is best at, and you, the developer, are left with a comfortable feeling that you’re not wasting your client’s valuable time or money.
Share your thoughts with @engineyard on Twitter