Implementing Localytics in Your Hybrid App

hybrid

Hybrid Apps

Hybrid apps have been the subject of much discussion in the mobile world, but what exactly are they? In short, hybrids are native apps which expose a significant portion of their functionality through an embedded web browser. This style of app allows app publishers to take advantage of some of the most compelling features of both native and web apps.

The native container allows for distribution through the app store, as well as access to the full range of rich, native APIs. These APIs enable the use of native UI controls, advanced rendering capabilities, and deep access to device hardware. At the same time, the web portions of the app allow for the "write once" cross-device portability afforded by web technologies. Publishers are also able to dynamically modify their web content without needing to push a new version of the app.

Localytics and Your Hybrid App

Localytics provides easy to implement analytics SDKs for both native apps and HTML5 apps, but what about hybrid apps? How should you add instrumentation to these?

An obvious choice might be to implement both the HTML5 and native SDKs. This approach presents a number of issues, however. Using two SDKs will result in two different datasets, each of which will identify your customer as a separate, unique user. In addition to duplicating much of your analytics data, this solution will prevent you from using powerful tools such as funnel analysis to correlate user behavior between the native and web portions of your app.

The best solution is to integrate the native analytics SDK, and then bridge measurements from the HTML5 layer directly into the native SDK. This implementation allows for all of the rich analytics capabilities of the native SDKs, is more maintainable, and correctly measures the customer as a single user.

The exact process for passing data from HTML5 into native code differs from platform to platform. On Android, it is straightforward to directly expose the Localytics API to javascript. I'll be focusing on iOS, where the process is more involved.

Hybrid Analytics with iOS

In general, passing data from javascript into Objective-C is accomplished by serializing the javascript action into a specially formatted URL. The native container will then intercept this special URL and parse the payload appropriately. This section will demonstrate how you can use this pattern to easily add instrumentation to both your native and web code.

First, create a javascript method which will take your measurement data and express it as a URL. We can use the JSON.stringify() method included in WebKit to do the heavy lifting for us.

[javascript]
function tagEvent(event, attributes)
{
if (event)
{
var redirectURL = "localytics://?event=" + event;
if (attributes)
{
var serializedAttributes = JSON.stringify(attributes);
if(serializedAttributes)
{
redirectURL += "&attributes=" + serializedAttributes;
}
}
window.location = redirectURL;
}

}
[/javascript]

Next, we need a way to pass this newly generated URL to the native part of the app. UIWebView's shouldStartLoadWithRequest method allows the webView to peak into the web requests flowing through it, and then decide whether or not to load the request. This is the ideal place to intercept and parse the URL.

[cpp]
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
// Intercept calls to the 'localytics' protocol
if ([request.URL.scheme isEqualToString:@"localytics"])
{
NSString *event = [self valueFromQueryStringKey:@"event" url:request.URL];
if (event)
{
NSString *attributes = [self valueFromQueryStringKey:@"attributes" url:request.URL];
NSDictionary* attributesDict = nil;
if(attributes)
{
NSData *attributesData = [attributes dataUsingEncoding:NSUTF8StringEncoding];
attributesDict = [NSJSONSerialization JSONObjectWithData:attributesData
options:NSJSONReadingMutableLeaves error:nil];
}
// Perform the native tagging call with the retrieved data
[[LocalyticsSession sharedLocalyticsSession] tagEvent:event attributes:attributesDict];
}
// From here, cancel the request. Don't let the webView try and load our custom URL
return NO;
}
return YES;
}
[/cpp]

I've created the following helper method, used above, for easily extracting query string key/value pairs:

[cpp]
- (NSString *)valueFromQueryStringKey:(NSString *)queryStringKey url:(NSURL *)url
{
if (!queryStringKey.length || !url.query)
return nil;

NSArray *urlComponents = [url.query componentsSeparatedByString:@"&"];
for (NSString *keyValuePair in urlComponents)
{
NSArray *keyValuePairComponents = [keyValuePair componentsSeparatedByString:@"="];
if ([[keyValuePairComponents objectAtIndex:0] isEqualToString:queryStringKey])
{
if(keyValuePairComponents.count == 2)
return [[keyValuePairComponents objectAtIndex:1]
stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
}
}
return nil;
}
[/cpp]

After implementing these methods, everything should be wired up. Start collecting some data in the webView using the new javascript method:

[javascript]
tagEvent('Level Up',
{'Level' : 6,
'Gold' : '25-50',
'Class' : 'Wizard'});
[/javascript]

And that's it. Log on to the dashboard and check out your new data.

The sample project containing all of the code referenced in this post is available on GitHub.

Author

Randy Daily is the Lead Mobile Developer at Localytics.

STAY IN TOUCH