Welcome to part 2 of this multi-part series of blog posts where we venture into the world of hybrid frameworks. This is where we get into the juicy stuff, if you are new to this, I suggest reading part 1 to understand the motivation behind this approach.
Big Picture Stuff
In a nutshell, what we’re trying to do here is mash a JSON response from a RESTful API call with a ejs (similar to rail’s erb) template to form the html to be rendered by a UIWebview. Simple, until you start asking questions such as how to an element to transition to a different state? How do you AJAX style interactions? How is template/asset syncing managed? These are all critical questions to ask of a hybrid framework. So before we dive into it, let’s make a short list of what a hybrid framework should be capable of before diving deeper into each feature separately.
A hybrid framework should:
* Maintain template syncing from a third party resource
* Handle bridging interactions between the web and native views
* Manage a RESTful API SDK
Maintain Template Syncing
At Miso, our mobile web templates are served off an Amazon S3 server. The basic strategy we employed is to download the web templates and cache it on disk, and when you open up the app in the future it would update it’s local templates with the latest and greatest by poking S3.
To accomplish this we used ASIHTTPRequest, a popular web request wrapper, to maintain web requests we send to the S3 server. This means asynchronous requests, cache responses, and a ASINetworkQueue that lets you handle multiple web requests.
There were a few nuggets we threw in worth mentioning here that made this strategy more efficient and scalable. We maintain a mainfest file on the server that has the list of assets the mobile device needs to download. The file would look something like this:
homepage.ejs?12398789 javascript/application.js?9437392 images/logo.png?1283740
We keep a versioning number by appending ?<some numbers> after each file. This is generated automatically every time we make updates to the web templates. Having this file allows us to scale well when we add more files, because all we have to download is the manifest file on startup.We then leverage ASIHTTPRequest’s cachedResponse flag and iterate through each file and check with s3 to ensure that this file hasn’t changed since we last fetched it.
To give a more concrete view of how exactly we maintain template syncing from a web request stand point, here’s a code snippet of an implementation of the delegate method where a ASIHTTPRequest completes showing the logic explained earlier:
- (void)requestFinished:(ASIHTTPRequest *)request {
NSString *responseString = [[[NSString alloc] initWithData:[request responseData] encoding:NSUTF8StringEncoding] autorelease];
// If the returned file is the manifest file, iterate through it and send web requests for each asset
if ([[[request url] description] hasSuffix:@"manifest.mf"]) {
// Separate assets and shove them into an array
NSArray *assets = [responseString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
for (NSString *asset in assets) {
// Get rid of trailing white spaces in assets list
if ([[asset stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] isEqualToString:@""])
continue;
// Add each asset in ASINetworkQueue
[self addUrl:asset queue:_queue];
}
} else {
// If it's asset file, templates on all views need to reload unless the response is from cache
_needsReload = _needsReload || ![request didUseCachedResponse];
}
NSString *file = [[request url] relativePath];
NSString *fqFilePath = [_localBaseUrl stringByAppendingPathComponent:file];
// If the file doesn't exist on disk, or the response is not from cache, then it's a updated file and we should save it
if (![request didUseCachedResponse] || ![[NSFileManager defaultManager] fileExistsAtPath:fqFilePath]) {
[self saveLayoutFile:[[request url] relativePath] data:[request responseData]];
_needsReload = YES;
}
}
I want to mention one last thing before I wrap up this section, and that is how we handle updating templates across different version of our app. Let’s consider the situation where you intend to release a new version of your app complete with the latest web templates. You realize that there are backwards compatibility issues of the new templates with an older version of the app. If you were to update the current web templates it’d break everyone on an older version of the app. Ooops! How we work around that is to maintain separate buckets of web templates for different versions of our app. It’s not always necessary, but something good to keep in mind.
Bridging Interactions between Web and Native views
Let’s elaborate on what this means via examples:
* User clicks on a link in a webview, and it pushes another UIViewController onto the navigation stack
* User posts a comment via a native controller, and we want that to fire off an AJAX request and render on to the web view
How Miso achieves this is by defining certain protocols to conform to when generating links within our EJS templates. That way, when a load request is captured by the UIWebView we can then route these requests to different parts of the app.
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType {
NSString *url = [[request URL] description];
// Check for protocol type and determine routing
if (navigationType == UIWebViewNavigationTypeOther) {
return YES;
} else if ([url hasPrefix:@"miso://ajax"]) {
[_ajaxController fireAjaxRequest:url];
return NO;
} else {
// RoutesController delegates native app actions given a miso://<controller>/<action>?<params>
[[RoutesController instance] processRoute:url viewController:_vc webview:self];
return NO;
}
}
The code snippet above is a stripped down version of what we use currently, but the basic concept is the same. We delegate routing by implementing the shouldStartLoadWithRequest delegate method for UIWebViews. So in the case where we capture a miso:// protocol we pass it along to other classes to process.
In the next part, we will start venturing further down the rabbit hole and look into the concepts behind RoutesController and AjaxController. Stay tuned!
Pingback: Hybrid (Native + Web) Mobile App Development • Part 1: The Motivation. | Miso Engineering
Thank you so much for sharing this, I’m looking forward to read part 3. Hint, hint…
I have that intention fully. :) Unfortunately we’ve been busy at Miso cooking up some cool new features so this would have to be put on hold until then. Thanks for showing interest and keep checking back for part 3!
Thanks lot for your kindly share the valueful experience for new guys. I start to realize your motivation and try to find a solution for it.
Hope to see your next part:) And I have to search how to bridge on android.