Shell Apps and Silver Bullets – A Rebuttal

I don’t want to get into the entire Web vs Native debate. However, a post by @sandofsky against shell apps (like PhoneGap) misses the mark on many of its arguments. I suggest you read the original post.

First I would like to say, in my opinion, the best use cases for shell apps are for apps that are primarily web apps or began life as pure web apps. I tend to lump shell apps and web apps together because I think a shell app ought to simply be a web app that’s simply enhanced with some additional device APIs. Any serious amount of device API usage and you’re probably better off going native. (Which probably doesn’t put me too far off from the author.) I don’t believe anyone is actually selling shell apps as a complete replacement for native apps. Or at least I’m not. I tend to believe native apps will eventually just die out on their own…

But on to the article…

The Framework Tax

If the native platform introduces a new API, or you run into an edge case that requires extending the shell framework, it could be months before you can implement your own app’s functionality.

The underlying pain point of the framework tax is if/when new APIs become available on a device, you’re stuck waiting for the shell framework to catch up. This is a legitimate concern but is being used as a straw man argument. Shell apps are aimed at people trying to create apps across multiple different devices. In which case, a single platform’s latest API is not something you can develop for anyway. If you’re creating a consistent experience across platforms, you have to wait for iOS to catch up to Android’s latest features and vice versa.

Browser Fragmentation

When developing a native iPhone app, the development cycle is: write a little code, run it in the simulator, and repeat.

The article only mentions the iPhone. If you’re only targeting iOS, then fragmentation is not a problem (market-share is). But as I’ve already stated (and will continue to point out), PhoneGap is for those who want cross-platform. If you’re targeting a broad market, then fragmentation is a given. In fact, targeting the browser actually reduces fragmentation because it has a more consistent API across devices than the native platforms do.

Shell apps require you write a little code, run it on an iPhone simulator, an Android simulator, a Windows Phone 7 simulator, et. al.

Once again, if you’re creating an app for each device, you’ll need as many simulators as platforms. This is pretty much a wash.

Versioning

Shell app let you update content without requiring a full app release by serving your pages off a server. In the process, you lose release atomicity, the assurance that whatever you ship to clients comes in one complete, unchanging bundle.

So the argument against versioning is: Can’t change the web portion of the app separately from the shell portion of the app?

Solution: Don’t have a ‘shell’ portion. If you’re using shell apps, the app should be just the web portion. If you need that much shell stuff that you actually have shell ‘chrome’, then stick with pure native. At that point, you’re no longer a web app.

Honestly, I’m surprised this is even listed as a con for shell apps. Versioning is one of the biggest wins for shell apps. It’s one of the biggest reasons that the web as a platform is so awesome. Immediate client updates. Period. No software delivery. No need to support old clients or deal with versioning of services because someone is using a 4 year old client and still hitting your server. If your app is a web app, then there is no such thing as an out-of-date client.

And speaking of versioning services… If your native app is still hitting your own managed services (and at this point, how many interesting native apps don’t hit the cloud in some way?), then you’ve just forced yourself to maintain versioned services.

The Uncanny Valley

Evaluated against native apps, shell apps have inconsistencies that make them feel wrong, even if the user can’t articulate the problem.

No argument here; that’s spot on. This is where the web platform needs a lot of work. I will, however, note that I don’t think web apps should try to look and feel native. Again, one of their biggest strengths is being cross-platform. Having a consistent look and feel whether you’re on an Android phone, iPad or Win8 tablet is something that shouldn’t be overlooked.

Performance

Maybe web technology will one day be as fast as native code.

This is the same argument that says Java is always slower than C and JavaScript will never be faster than . While performance is a huge problem for web-based apps, you can’t dismiss it out of hand. Many times you actually get performance improvements by running on a VM. (hotpsot optimization, JIT).

Of course, this partially goes in hand with using UI libraries in order to look native. If you drop that hacks that try to emulate native feel, most of your performance problems disappear.

Additionally, native apps have the same network bottleneck as web apps when they need to hit services, so that’s a wash. And web apps have numerous faculties for caching; they just require some experience to get right.

Fast Release Cycle

The time spent designing, developing, and verifying functionality dwarfs the time spent in an app store review process.

No, the app store review process doesn’t impact your development cycle if you only release once a month. But if you’re running continuous deployment (a desirable if lofty goal), then releasing an app update multiple times a day is ridiculous. However, releasing a web update hourly is perfectly fine. Bug fixes? hours, not weeks. Try A/B testing? Get instant feedback. And don’t overlook the annoyance factor that your users encounter when they get an app update every week.

Write Once, Run Anywhere

Fifteen years ago Java promised cross platform client development. […] Count the number of apps in your dock running Java.

So the web platform must be a failure because Java failed? Sorry, that doesn’t cut it. The web platform is the closest thing to single deployment that’s ever existed. This is primarily due to its ability to tolerate broken-ness and degrade gracefully. Yet how many versions of native apps would you have to develop to support the various API/device feature flavors of Android? And that’s just one platform!

HTML5 Is Easier

Every language has warts. […] Today, for high level work, I am as productive using Objective-C as JavaScript.

Yes every language has warts. Stick to your strength. BUT: How many developers know Java and Objective-C and .NET. How many can develop across all these platforms simultaneously? Once again, if you only care about one platform, then go native. If you want reach, one single platform is definitely easier.

It’s harder to build an app with web technology. The web began as a document format, with an interactive layer added later.

I think the “web as a document platform” is old-hat at this point. JavaScript is a first class language. The new and coming HTML5 APIs offer a compelling feature list in a very malleable and powerful language.

And again, the layer-ability of web technologies is an incredible asset. How many mash-ups exist between Java applets, Flash sites, or Windows desktop apps? Going with the web platform opens up so many possibilities. Take advantage of the ability of your app to spawn other apps, which may even improve your own!

Silver Bullets

No, there are no silver bullets. And I agree that there are many, many strong use cases for native apps. But the problem with this article is that so few of the arguments put forth are actually relevant. You shouldn’t be deciding to go native or go web based on the Framework Tax, Performance, Versioning, Fragmentation, or anything else. Start with what you want the app to do. Go native, if you must. But think long and hard about the benefits of using the universal platform. Think about the device upgrades in the next 6 months. Will your native app work out of the box on the latest OS? What about the next wave of devices the following year (refrigerators, car systems, washing machines, kiosks). What device APIs will they have? What features will they have? No one knows. But one thing you can bet on: they will have web browsers.

Google Analytics Tagging with HTML5 data-* Attributes

Imagine if you will, a page with a significant amount of dynamic page elements. For instance, a slide-out panel containing a number of ‘panes’ containing topical information on various ‘factors’. Let’s assume that once this slide-out is open, we wish the user to be able to jump from factor to factor (similar to a slideshow) using buttons along the bottom of each factor. Let’s go even deeper and say that within each factor, we have a collection of close-up images. Again, the user should be able to navigate the close-ups via next/previous buttons similar to a slideshow. In addition to the next/previous buttons, along the top edge of the ‘close-ups’ section are progress indicator dots that highlight according to which close-up is active (and link directly to individual close-ups).  Given that this functionality should work without JavaScript, all of these UI controls are marked up as links. And we have quite a few of them—to wit, x*(x-1) factor links (for x factors) plus x*y (direct links to close ups per factor) plus x*(2y) (next/prev links for each y close-up for each factor). So, let’s say we have 3 factors and 3 close-ups per factor. We now have 33 links! And now the task is to tag each of these links with unique labels that will be sent back to Google Analytics upon each click event. I’ve broken down a brief subset of the analytics tags for each of the three types of links below.

Tag Templates

Tagging template for the factor navigation along the bottom of each [x] factor:

/page_name/factor_[x]/factor_1_icon
/page_name/factor_[x]/factor_2_icon
/page_name/factor_[x]/factor_3_icon

Tagging template for the prev/next buttons on each [y] close-up on each [x] factor:

/page_name/factor_[x]/closeup_[y]/next_arrow
/page_name/factor_[x]/closeup_[y]/prev_arrow

Tagging template for the close-up direct links (also used as progress indicator) on each each [x] factor:

/page_name/factor_[x]/closeup_dot_1
/page_name/factor_[x]/closeup_dot_2
/page_name/factor_[x]/closeup_dot_3

Approach

As with anything, I try to keep my code DRY. Considering that these tags will likely end up as magic strings in some form or another, I’d like to reduce the maintenance overhead of these tags as new factors or close-ups are added or removed. The tags themselves convey the hierarchy of the structure in which they are contained. So let’s map the tagging hierarchy onto the structural hierarchy. This tagging information could be embedded in the id or class attributes of an element, though I think that would be coupling two separate needs (JS behavior/CSS styling + Analytics) onto the same data. This information could also conceivably go into the title attribute, though this attribute is meant for human (read: end-user) consumption. Nothing seems to fit, so let’s try out an HTML5 data-* attribute: data-ga.

First, the /page_name segment should map to the page:

<body data-ga="/page_name">

Each ‘factor_[x]’ segment should map to its own factor pane:

<ul class="factors">
  <li data-ga="/factor_1" />
  <li data-ga="/factor_2" />
  <li data-ga="/factor_3" />
</ul>

Each ‘closeup_[y]’ segment should mapt to its own close-up pane:

<ul class="closeups">
  <li data-ga="/closeup_1" />
  <li data-ga="/closeup_2" />
  <li data-ga="/closeup_3" />
</ul>

 

And each link or button gets its own respective value. Keep in mind, multiple data-ga values will be ‘scoped’ by their ancestors’ data-ga values:

<a href="#factor-1" data-ga="/factor_1_icon" />
<a href="#factor-1-closeup-1" data-ga="/closeup_dot_1" />
<a href="#factor-1-closeup-2" data-ga="/next_arrow" />
<a href="#factor-1-closeup-3" data-ga="/prev_arrow" />

Now, whenever a link is clicked, we simply concatenate the data-ga values from each ancestor! The method below should have the context of this as an anchor element with a data-ga attribute. Generally, it would be in the click handler of any element matching the selector: "a[data-ga]". Once ga is concatenated, it can be used as the tag for a Google Analytics API call (_trackPageview or _trackEvent).

$("a[data-ga]").click(function(event){
  var ga = $(this).parents('[data-ga]').andSelf()
           .map(function(){return $(this).attr('data-ga');});
  ga = $.makeArray(ga).join('');
  _pageTracker._trackPageview(ga);
});

Demo

//jsfiddle.net/eY4cq/1/embed/

A few additional notes

Performance

It would be much better to calculate the ga-tag for every link on the page during page load and cache the result in the data store of the element. As written, the ancestor traversal is executed on every click, and would result in the same return value each time. However, as a special case when I was first implementing this, there were some widgets that altered the hierarchy of the page, thus it was necessary to only perform the concatenation at event-time rather than load-time.

As an additional performance boost, I added a class of ga-scope to each ancestor element that contained a data-ga attribute. This allowed me to use a class selector in the .parents() filter. This will only yield a performance boost in browsers that support a native implementation for querying by class name, thus allowing the selector engine (MooTools or jQuery) to avoid stopping to inspect every single ancestor on its way up the tree.

MooTools

I originally implemented this with MooTools. During the implementation I discovered a bug in the MooTools selector parser where the attribute selector doesn’t properly find attributes with hyphens. So, I created a custom pseudo-selector as a workaround:

Selectors.Pseudo.data_ga = function(){
  return Boolean($(this).get('data-ga'));
};
$$('a:data_ga').addEvent('click', function(event){
  var ga_code = this.getParents('.ga-scope')
                .get('data-ga').reverse().join('')
                + this.get('data-ga');
});