Shell Apps and Silver Bullets
Every company comes up with the idea of writing a “shell app.” By replacing native code with a web view you could:
- Release new functionality outside a full app update
- Implement a feature once and share it across platforms
- If you come from web development, you can stick to a familiar stack.
I reached out to great engineers to hear their experience with shell apps. Everyone told the same story.
At first things are easy. For simple screens, using a webview might be faster than writing a native implementation. As you add functionality to the webview, the complexity increases until you give up and write everything native.
This post describes a few risks in shell apps, but many of these risks are found in any abstraction or alternative to the platform’s first-class approach. If you plan to write an app for iOS or Android, you will save time and create a better product if you stick to Objective-C and Java, respectively.
The Framework Tax
Shell apps require a bridge for their pages to interact with native chrome and call native APIs. Where does it come from?
You could use an open source shell-app framework like PhoneGap, but that leaves you at the mercy of their schedule. 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. One solution is to start from an open source framework and maintain a fork when the need arises.
Regardless of where the framework comes from, if the code is in your app your team is accountable for it. Time spent designing, coding, testing, and documenting glue code is time not spent on user facing features.
When developing a native iPhone app, the development cycle is: write a little code, run it in the simulator, and repeat.
Browser quirks live on in HTML5. Shell apps require you write a little code, run it on an iPhone simulator, an Android simulator, a Windows Phone 7 simulator, et al. Alternately, you could save cross browser testing for the end, but the risk is missing architectural mistakes until late in development.
Shells apps promise to let you share a single implementation across several platforms. In practice, they trade several straightforward implementations for a single complex implementation.
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.
Imagine 1.0 of your app has a red color scheme. Between 1.0 and 1.1, your company changes its branding from red to green. The new design touches both your web view and the native chrome around the web view.
Users may wait weeks or months to update apps. If you don’t coordinate things, the user will see the web portion in green, while the native portion remains red, giving your company a Christmas theme.
One solution is versioning pages, with the client including a version number with requests. The consequence is that whenever you touch code shared between versions you need to ensure every prior UI doesn’t break.
The Uncanny Valley
Within a browser, users hold web sites to the standards of other web sites. Evaluated against native apps, shell apps have inconsistencies that make them feel wrong, even if the user can’t articulate the problem.
On iOS, the scroll friction for web views are different than native scroll views. WebKit delays taps to detect gestures. On Android, the web view may add decoration to link clicks. Platform specific quirks can be remedied, but it’s a matter of trial and error.
The harder problems are inconsistencies in platform conventions. The iPhone uses Helvetica as its system font, while Android uses Roboto. iOS transitions between views by sliding new views in from the right, while Android uses a zoom effect.
You could recreate each platform in HTML5, but that requires a lot of work. You could design everything around an iOS look, and settle for things looking weird on Android. You could create a platform agnostic design language, making things equally weird for everyone. In a web view, it’s an uphill battle for consistency, whereas going native provides consistency for free.
Some software can succeed with poor performance. My go-to language for web app development is Ruby, which is slow, but a 60 millisecond difference goes unnoticed in a web app, where most of your time is spent waiting on the network.
For native apps, performance is critical to a great user experience. Users notice jerky scrolling, and performance can make or break a feature like a full text inbox search.
In performance critical situations in Ruby, I drop down to C extensions. Web technology has no way to get to the metal.
Maybe web technology will one day be as fast as native code. That day isn’t within the lifetime of the app you are writing today.
Solving the Right Problems
Sometimes the ends justify the means, but the reasoning behind shell apps is generally flawed.
“We Need to Release Faster”
The web can easily push out new content, but don’t confuse content with functionality. The CNN homepage pushes out content dozens of times a day. It’s trivial for the Huffington Post to A/B test headlines because it’s cheap to write a headline.
If Facebook wanted to let you “poke” brands, that’s functionality. Engineers have to design an architecture that scales to hundreds of millions of users. Product managers have to figure out how it interacts with existing privacy settings.
The time spent designing, developing, and verifying functionality dwarfs the time spent in an app store review process. It isn’t worth cutting a few days of deployment if the infrastructure to support it slows down your overall development pipeline.
“Write Once, Run Anywhere”
Count the number of apps in your dock running Java.
“HTML 5 is easier”
It’s harder to build an app with web technology. The web began as a document format, with an interactive layer added later. The web was optimized to reduce publishing friction, with platform agnosticism for maximum reach. You can’t optimize web technology for an app experience without compromising its strengths for the web.
I used to think the solution to the web’s gotchas was a framework to conform the web to a native app mentality. I built and deployed web apps with these stacks. I would never do it again. These frameworks build abstractions on top of abstractions. In every non-trivial app I worked on, the top abstractions leak, and you still have to hack HTML and CSS.
Web technology is great for many things. Replicating a native app experience is not one of them.
When people want a native app, they are asking for an app user experience, which is more complex than the web experience. For instance, users want apps to load immediately. That requires a client side cache, which is inherently more complex than a stateless client. There are no silver bullets to solve essential complexity. Trying to abstract away essential complexity only makes things more complex.
Sometimes the quest for a silver bullet comes from a good place. Technology is about removing mindless work. Great engineers go through a phase where they try to create the ultimate framework to remove their daily grind, only to realize it’s more work to maintain a gigantic framework.
The difference is shell apps come from the wrong mentality. They start from, “How do we reduce effort?” instead of “How do we deliver the best product?”
Great products require more work. They require commitment, attention to detail, and leaving your comfort zone. Shortcuts are just a distraction.