When we launched the iPhone version of Basecamp in February of last year, it was after many rounds of experimentation on the architectural direction. The default route suggested by most when we got started back in early/mid-2012 was simple enough: Everything Be Native!
This was a sensible recommendation, given the experiences people had in years prior with anything else. Facebook famously folded their HTML5 implementation in favor of going all native to get the speed they craved with the launch of their new app in August of 2012.
Thus their decision was likely driven by what the state of the art in HTML and on mobile looked like circa 2010-2011. In early 2010, people were rocking either the iPhone 3GS or 3G. By modern 2014 standards, these phones are desperately slow. Hence, any architectural decisions based in the speed of those phones are horribly outdated.
Decisions based on computing speeds quickly decay
With that in mind — that performance data driving previous tech choices was outdated — we set about to do a series of bake-offs. We implemented the main progress screen in the iPhone app in first a fully native version, then again in an HTML-backed version.
After a fiddling a bit, the conclusion was clear: There was no discernible difference! Well, except for the fact that it was far quicker to develop the HTML version than the native version.
Naturally, this isn’t true for all domains. If you’re programming a 3D game, you want every ounce of raw-metal speed to improve the experience. But if you’re building an information system like Basecamp, you’re looking at least as much at programmer speed as you are computing speed. And frequently, it’s well worth trading the latter for the former. So we did.
1st Gen: Native shell around server-generated webviews
Basecamp for iPhone is a thin wrapper of native navigation around essentially a webview core. This webview is powered by the regular Basecamp Rails application, with only a few stylistic differences from our normal mobile web view. But the difference is still profound.
Having a native shell makes the feel of the mobile web site very different indeed. It let’s people find the app via the App Store, and their login persists forever (rather than the frequent cookie losses you seem to get on mobile Safari). Users have loved it. The app has a 4/5-star rating.
And we loved building it! The iPhone app was mainly built by a single programmer and a single designer. Since we already had a wonderful mobile web site that could be repurposed, the effort was easily manageable.
Something that could not have been said if we were trying to build all of the Basecamp functionality natively, from scratch. A team of 10 probably wouldn’t have been enough, even if they had worked for 18 months. We were simply orders of magnitude more productive by using the web and HTML as our main runtime for the native app.
2nd Gen: Native shell + native navigation
That was the first generation mobile app for Basecamp. The release of Basecamp for Android just a few months ago was our second generation. And the level-up is significant.
What we learned from the first-generation iPhone app was the power of native navigation, but it wasn’t until the Android app that we went all in on that. In the Android app, we’re using native contextual menus — automatically constructed from data in the HTML views — to make it feel even better. It’s to the point where it’s hard to tell where native ends and HTML begins.
Again, the development of the Android app happened with a tiny team. 1-2 developers and about half of a designer. We were again able to reuse all those mobile webviews that already powered the regular mobile web and iPhone experience. A huge productivity jump. And again, users love it. The app has a 4.5/5-rating from more than 1,000 users.
While many companies are grumbling over just how slow the development of native iPhone apps can be, even more seem to do so with Android. Maybe because they’re just used to the iOS workflow, maybe because of all the different Android variations, but for us it didn’t even matter either way. We were able to put out a fantastic version of Basecamp for Android by reusing 95% of the work we had already done, and we didn’t need to balloon our team or company to get there.
Spending the productivity spoils on native where it matters
We’re now working on our third-generation mobile app for an as-of-yet undisclosed platform (although I’m sure it won’t take much extrapolation for you to guess!), and we’re going a step even further. From 1G to 2G, we doubled down on native navigation, while reaffirming our webview-core architecture. In the jump to our 3G app, we’re taking our productivity spoils and spending them where they can make a difference.
The start of that is by picking a part of Basecamp that really can benefit from high-fidelity native UI and making it so. Where we before had 100% of the meat of the app in HTML, we’re now going with 90% HTML, 10% native, and really getting the best bang for our buck by picking the most worthy 10% to go native with. The integration feels really nice and seamless, and you may well not even notice at first which parts are HTML and which part is native. Which is exactly the point.
The technology underpinnings of our hybrid approach
The technology behind this is all fairly straight forward. The main hurdle on the native side is dealing with the webview integration and controlling the loading phase, as well as the cross-links between native and HTML. We’ll try to share more about these techniques soon, but it’s a lot simpler than you’d think.
On the HTML side, we’re running a proud and polished monolith of a Rails application. The same controllers and models that are delivering the desktop web experience are responsible for the mobile views, which in turn are integrated into the iPhone and Android clients. This is made trivially easy by the Rails 4.1 feature of variants.
This also makes the rollout of new features endlessly easier. Imagine if we had to change a Rails desktop app, a Rails API app, a client-side MVC app, a mobile web wrapper app, an Android app, and an iPhone app whenever we wanted to improve or introduce new functionality! That’s simply not a workload that’s realistic for a team of 10 programmers and 7 designers.
Besides the much-reduced workload, it also means we’re able to fix problems much faster. When the majority of the functionality lives in the HTML served from the server, we don’t have to wait for Apple’s multi-day approval cycle to fix things. Maintaining the native apps become more like maintaining the web through continuous deployment.
As mentioned earlier, this hybrid architecture might not work for everyone. It hardly worked for anyone in 2010 because phones were too slow, so the HTML/JS underpinnings meant worse performance, and users didn’t like that. But times have changed. Modern phones are incredibly fast, and you can even run a fair amount of JavaScript on your mobile views without hurting perceivable performance.
Hybrid disruption of the all-native paradigm
So in classic disruption theory, the hybrid model is eating its way up through the complexity chain. I’d argue that the majority of information-based apps today can be successfully implemented through this approach with varying levels of the native/HTML split.
It also makes it easier for small teams to break out of the iOS-only bubble when it comes to mobile. When you don’t actually have to recreate the entire app, and you’re not just doubling the maintenance burden, it becomes a lot easier to go multi-platform from the beginning. Or at least much faster to add new platforms as you go along.
I know there’ll be plenty of skeptics of the hybrid approach. Either because the context they work in requires lots of native fiddling (or they at least think it does!). Or perhaps because they spent so much time perfecting their UITableView chops that it seems a shame not to flex those muscles at every chance. Or perhaps because it served the big-company moot well to have mobile development be so much harder and more time-consuming than web-development.
But whatever the reason, I think every software shop today should take another look at the hybrid approach. You might well find the time is right for a switch. If so, congratulations, and enjoy!
Mike Waite
on 08 May 14I’m curious how you will evaluate which parts of your product that will benefit from high-fidelity native UI and how that differs between devices? The decision to cycle this growing process from one device to another is very nice to see. It’s obvious you are not trying to hit device parity, but you still offer a working product across your supported devices. Love it.
DHH
on 08 May 14Mike, it’s not scientific. It’s mostly on the feel. The best way is to do via a bake-off. If you think something would be better native, just do a spike in both. We’ve multiple times thought something would be nicer in native, then did the bake-off, then changed our mind.
Where it’s more clear cut is when you want to use device functions. HTML still isn’t quite there for using the camera or other sensors. It probably will in time, though, so don’t set that bar as a permanent marker (like some did with the performance issue!).
Mike Parsons
on 08 May 14Excellent article David.
Curious if you used a container like PhoneGap or Cordova or did you roll your own?
Did you evaluate any other hybrid containers?
DHH
on 08 May 14Mike, we didn’t use any containers. There wasn’t that much code in the wrappers and we wanted to understand that 100%, so we could tailor it like we saw fit. There was no reason to get greedy, in our view, since we had already shaved off 95% of the effort. Haggling over the last 5% seemed a little petty :).
But I do think in the future we’ll see containers that are Good Enough for many people for much of the work. So even rolling your own container won’t be necessary. Maybe that’ll be 4G for us ;)
Anon
on 08 May 14Thanks for sharing David.
Did you stick to RubyMotion? Are your developpers still happy with it?
Mark Nutter
on 08 May 14I was on a project called kona.com where I suggested we take the same hybrid route and it worked really well for us. We did use Phonegap which allowed us to take advantage of some plugins for things like push notifications, camera support, etc. Although it was Rails on the backend we actually used Angular.js inside the mobile app which worked well but I’m wondering if 37Signals’ approach of just serving the views up with Rails as normal isn’t a better approach, especially since you can update those views without going through the App Store review process (although I suppose we could have served the assets remotely but then we would have had to worry about network issues). UIWebView’s JS engine doesn’t enable Nitro so there’s a performance hit there, although we didn’t notice it often.
We also took the same approach 37Signals did when it came to determining what would be native. We knew we would need to make the top and bottom nav bars native because we wanted to lean on the native scrolling of Android and iOS’s webview components (especially important for older Android devices). We also had Facebook style drawers done natively so we had three different angular apps running in the three different drawers and everything stayed silky smooth. But our mantra was to do it in html5 first and then rebuild it in native if it wasn’t a good experience. More often than not the html5 version of a feature or UI element was perfectly acceptable. It only took one developer (myself) to maintain the native code on all devices.
If you download and try Kona on your Android or iOS phone and you have a trained eye you would be able to tell its hybrid. But the average user in my experience was never able to tell, and that’s what really matters.
David, do you take advantage of localstorage or any other local caching of content at all? Our angular app saved everything off into localstorage so that we could technically allow offline access, although we never got around to enabling that feature. Also, will you be doing a deeper dive at some point into how you guys implemented it?
Chris
on 08 May 14What position did you take on offline support? Do you allow users to work offline and sync or mandate that they have to have a good connection?
DHH
on 08 May 14Anon, probably wouldn’t use RubyMotion again. The shell is small enough that the trade-offs don’t seem worth it. We’re also often fiddling with low-level stuff, so it’s better to be as close to the metal as possible for that.
Mark/Chris, we have not bothered with offline access. Basecamp is inherently a collaborative app. We want people to have access to the latest. That’s probably different if you build something that just has 1 user. But for us it hasn’t been worth touching the synch problem.
Derick
on 08 May 14@DHH
How do you deal with Android browser engine being unbearably slow to render?
This is a common complaint on Android and a driving force as to why so many more Android apps are native vs webview.
DHH
on 08 May 14Derick, how recent is your experience? The Android Basecamp app on my Nexus 5 and HTC One absolutely flies. It uses this technique. Maybe the performance issue is no longer a concern?
Derick
on 08 May 14@DHH
Fairly recent.
I suspect it might be related to how little you use javascript (IIRC on how you’ve developed your app).
Because JavaScript performance is, in my experience, horribly slow on Android.
In case your curious, linked below is more information on this topic.
https://www.timroes.de/2013/11/23/old-webview-vs-chromium-webview/
DHH
on 08 May 14Derick, we use a fair amount of JavaScript, but certainly nothing as bothersomely heavy as a client-side MVC framework. It does run Turbolinks though :)
Beau
on 08 May 14I really needed this reminder.
We ended up going native a few years ago because of performance, and the need for some offline and camera functionality. Overall it worked out well although the engineering effort across Android and iOS is huge. Esp. debugging and bug fixes. Not to mention iOS app store delays.
We currently have a few new features that need to be added to our app, mostly complex forms. I have been stuck in the mindset that everything needs to be native, and it really doesn’t.
I think your approach will work for us and save me considerable time.
There are probably other parts of our app that could move back to web as well.
Nice post.
Charlie Magee
on 08 May 14Typo: “we set abound to do a series of bake-offs.”
. . . we set about
Thanks for the article.
Patrick
on 09 May 14Thanks for the article, it’s hard to believe that the Android app isn’t native and that’s coming an Android developer! The navigation drawer and view transitions make it feel 100% native.
Jaydee
on 09 May 14Any chance you’d publish a technical write-up about the shell you developed? Or even open source it? Would really appreciate that. I’m a web developer and really looking into creating a mobile app (currently, best I can do is make the layout look nice in mobile), but I don’t have the time and expertise (yet) to delve into iOS. Would really appreciate this if you publish it.
Nick
on 09 May 14@Derick, your experience may be based on older versions of android where the WebView was based around the stock browser. In newer versions of Android (I can’t remember exactly, but maybe since JellyBean) the WebView is based around Chromium so performance is much better.
@DHH given this (assuming you haven’t already), it may be worth testing on a few older versions of android to ensure that performance isn’t unbearably bad
David
on 09 May 14Nice Article! In this point I guess hybrid development can only get better, though as opposite to iOS, old Android devices market share is still huge. In our case we developed our own bare bone hybrid container, which was proven to be used seamlessly accessing native features and basic navigation without the need of third party heavy weights as Cordova. Nevertheless, I wonder how you approach the webview navigation, since I find the navigation history handling(native stack, web history) is a common source of pain. Our latest approach was to simplify de web navigation by using a single webview for each screen and handle the transitions in the native scope, so no webview history there… What is your current approach in this issue?
DHH
on 09 May 14Nick, good point. We wouldn’t change our approach, though. In the near future every Android device that we care about is either going to be upgraded to or replaced by a jellyfish capable version.
Frank Neulichedl
on 09 May 14Thank you for the write up. This hybrid solution is in place in so many apps right now and it mostly sucks because they in addition to use webviews, they don’t cache the content.
Hybrid sounds good, but right now I see it used for Settings (yes settings of web service changed through the app) and about screens and for serving up mobile views of websites. This are actually the places where I expect an immediate interaction and not to wait for a page to load.
Settings especially feel weird, but also normal webviews of website pages make me think why even bother using a native app.
Hybrid can be ok if you take advantages of background syncing of content to avoid any kind of lag and style the html to look good. But then the effort to go full native doesn’t seem to much if you just use it to display content.
Rimantas
on 09 May 14“If the only tool you have is a hammer…”. This post ignores so much it is not even funyy. Sure, when you have HTML ready WebViews are a very good choice to have it. However there than enough reasons to be sceptical about hybrid approach.
Grover Saunders
on 09 May 14Okay, genuinely not trying to be an ass here (which is part of why I’ve never posted a negative review of the app on iTunes), but I find it a little frustrating to hear you guys repeatedly crowing about the quality of the iOS version of Basecamp. It is without question a second-class experience, notably clunkier than similar apps and very buggy for quite some time (this has improved recently to be fair). My favorite bug was when you’d rotate the screen with the keyboard open, and the content would rotate but the keyboard wouldn’t with no way out but to force quit the app.
And let’s be clear, if the tone was “We didn’t have the resources, so we did the best we could.” it wouldn’t be nearly so galling. But the tone of your writing implies that you’ve brilliantly hit on the perfect strategy while everyone else wastes their time, even though if you actually read the reviews on iTunes, many those four stars reviews could be summarized as “Basecamp as a whole is great, this app is better than nothing.”
Emil
on 09 May 14Too many companies skip the mobile browser views. It shouldn’t be necessary to create native apps for this kind of app, but it is. Especially since Apple keeps makes Safari less compelling for web apps.
I’m on iOS and I use Basecamp via Safari more than the app since I get there by email notifications. The power of bookmarks/urls in also something missing in native apps. I love being able to open up a bookmark which points me to a global to-do lists in Basecamp that keeps track of all my current projects.
Jason Fried
on 09 May 14Emil, yes… The #1 native Basecamp app is whatever email app you’re using on your phone. Click a link in the email and it’ll take you directly to a really well optimized web view for that device.
Pedro Cardoso
on 09 May 14I’m a native iOS developer during the day, but after work I’ve been thinking more and more about this subject. Sometimes it seems that we’re moving into the API a lot of information about the content, how to present it, when to hide it, etc, when it would be far easier just tell the native app: “here’s some html, show it and get it over with”. YMMV of course. This is not something for all kinds of apps, but when it is and it gets the job done, who cares?
I’m taking this approach on a app I’m currently working on, native navigation with tabs, navbar buttons, push/pop native animations, modals, the works, but the main content is html and shared between iOS and Android.
I’m also trying to write an ebook about these concepts and I wrote a blog post about doing a simple native wrapper. In case you want to take a look, it’s here.
Matt
on 09 May 14Really interested in reading a blog post about the tech behind this application. Also very interested in reading how you handle different views for mobile and desktop. I assume it is responsive and so you only maintain one set of style sheets for the mobile/desktop views.
Jason Fried
on 10 May 14“I assume it is responsive and so you only maintain one set of style sheets for the mobile/desktop views.”
We currently maintain different views+styles for desktop and mobile. We find it allows us to design more appropriate UIs for mobile web. The controllers are the same, but the views are different.
Kevin
on 10 May 14How would you go about extending this and supporting offline access?
Jikku Jose
on 10 May 14Interesting to read about using such a hybrid approach. Have been thinking of such a hybrid solution for quite long. But I had dropped the idea as it really bothers me to see apps’ views hanging to load the layout once in a while. Perhaps it can be attributted to significant network latencies I notice (from India).
In fact this approach isn’t new. iTunes have been using this in iOS & Mac for years; the main reason I hate browsing the AppStore. Not bitching about the solution, but I really think there is a significant use case aspect to highlight here: apps requiring media caching & offline mode in my opinion should not use this. This might be ideal for a collaborative tool like Basecamp or even something like Readability/Clearly. Since, loading a view like a web page(in parts) breaks the experience heavily.
DHH
on 11 May 14Kevin, this approach is not compatible with offline. We have all the logic for creating and displaying on the server. Thankfully offline is close to a non-issue for Basecamp.
Silver
on 12 May 14I’m wondering, if you also considered using something like Xamarin? In theory you get a fully native app on both iOS and Android (and Windows Phone in the future) with a mostly shared codebase.
DHH
on 12 May 14Silver, cross-platform reuse is just one of the many benefits to the hybrid approach. Being able to share the same code base between web and native is another. As is working in our preferred development environment. Xamarin would solve none of this.
PJ
on 12 May 14@DHH: from a development/technical standpoint this makes total sense. But I think it is missing an important point besides the pure performance issue: by going hybrid, whether you want it or not, you are locking your mindset in a “pointer and keyboard” approach. You end up “tweaking” a desktop system to adapt it to a multi-touch interface, instead of being able to rethink it for a totally different type of platform. So if your goal is to give your users a mobile point of entry to a desktop app it is probably fine. Otherwise, it could lead to a missed opportunity…
Joe
on 12 May 14How did you guys deal with the fact that not only are the view layouts different in mobile vs desktop, but also the route paths? For example, say you have a product with many reviews. On desktop this is all on one page, and the route ends with the product ID (e.g., /product/1). However, on mobile you don’t want the user scrolling forever on the product page to see the reviews, and instead provide a button/nav link to see the reviews. This necessitates an extension of the route path (e.g., /products/1/reviews).
We too are thinking we’ll need to have separate markup templates for mobile. Are you conditionally loading the mobile vs desktop html depending on where it’s accessed from?
DHH
on 12 May 14PJ, that doesn’t have to be true at all. You can optimize your mobile views for touch, not point, click, and hovers. That’s how we approach it.
Joe, we occasionally have certain actions that are only used for mobile or only for desktop. The win is to share the bulk of the work. It’s fine to have extra on the side too.
Lukas
on 12 May 14This approach looks very interesting!
One question: How do you deal with partials, that shouldn’t get rendered on mobile? For example: I often include a “navigation” partial in my application.html. If I use native navigation this shouldn’t get included.
PJ
on 12 May 14DHH: “You can optimize your mobile views for touch, not point, click, and hovers.” That’s what I meant: you can “optimize”, but it is really hard to look at things from a new, multi-touch/sensor based angle. Think about the Clear app (I am not judging its quality or pertinence here, just the principle): I doubt this sort of UX thinking can happen when you are adapting an existing site. iOS is pushing us to rethink this stuff, and I think it is really important.
JZ
on 14 May 14@Lukas – You just make a new application.html for each variant. That’s key because it’s where you include, for example, your phone.css and phone.js instead of the normal full site assets.
Pablo
on 14 May 14I’ve just tested the app on iOS7 (iPhone 5S) and even though I’m pretty impressed with performance and native feel, the “slide to go back” gesture is broken.
If this behaviour could be implemented smoothly then there’s no gap missing for hybrid apps on iOS.
Glenn
on 14 May 14DHH: It’s been nice to see such great responses to so many commenters, but you missed Jaydee asking about the technical writeup, and that’s the answer I really want to see!
I have a Rails 4 app using Turbolinks and I love it, but all of the current mobile wrapper frameworks would require some very serious rebuilding, for example how Phonegap needs it to work as a single-page app. I’d love to see how you all implemented your wrapper, especially the part about keeping navigation native.
I understand it would be extra work to create a new open source project out of this, but I’d settle for a downloadable zip-file of what you’re willing to share. I think many Rails enthusiasts like me could really get a lot of milage out of this.
JZ
on 14 May 14@Pablo – the slide-to-go-back gesture is unsupported by design because of the page stacking UI in Basecamp for iPhone which precedes iOS 7. It has nothing to do with the hybrid approach there just wasn’t time to completely re-think our design to support it.
This discussion is closed.