Posted by Garrick Toubassi, Engineering Director
Editor’s note: Be forewarned that the following post has much more technical mumbo-jumbo than our normal fare, taking you behind-the-scenes of the development of Inbox. So if you’re a practicing engineer, an aspiring hacker, or just plain interested in knowing how the sausage is made (mmmm sausage), read on!
Gmail was born over 10 years ago, entering a world dominated by flip phones, trucker hats, and based on today’s standards, sluggish web applications. Every click on a webpage meant a multi-second wait and a full page refresh. So when we developed Gmail, we took a different approach—building a new genre of web app that ran in the web browser and relied on rich javascript logic and a local data model. This allowed many of those clicks to be handled right within the browser without waiting for the server at all. Fast forward 10 years and this architecture is the norm, having been adopted by most websites and supported by a plethora of frameworks and tools (e.g. AngularJS, Meteor, Backbone, Ember, NodeJs).
But in those same 10 years, a lot has changed. The capabilities and diversity of devices has exploded. Users expect to be able to move from a laptop to a phone and have their apps work flawlessly. As a result, developers are facing a new challenge: how to build a high-quality app across platforms, such as Android, the web, and iOS, without sacrificing quality or execution velocity. As a developer, maybe you’ve asked yourself, do you rewrite your app three times to optimize it for each platform, wringing out every last bit of performance and polish? Or do you aim to get the app to market sooner by building a web-based “hybrid” app that leverages the same technologies across platforms (but potentially sacrifices integration and user experience)?
Facing the challenge
Those were the questions that weighed heavily on us when we first started building Inbox. We’d been working on Gmail for years and knew our users would expect whatever we built to be as fast and polished as Gmail is today right out of the gate. And that led us to the decision to build three separate native apps to fit seamlessly into each of our respective target platforms: Android (via Java+Android SDK), web (via JavaScript+DOM/CSS), and iOS (via Objective-C+UIKit).
Of course, there are a number of elements of Inbox that are shared across the three platforms: code for managing network communication, caching objects, local persistent storage, managing user edits both locally and remotely, and supporting it all while offline. This logic must be faithfully and correctly implemented and kept up to date on all three clients. Rewriting it three times in three different languages would soak up substantial engineering resources and slow down how quickly we make improvements to Inbox.
Cutting the Gordian Knot
In order to address this challenge we took a novel approach in which data model and application logic (conceptually the “Model” in “Model-View-Controller”) is written once in Java. This data model abstracts concepts unique to Inbox like Conversations, Reminders, Contacts, and Labels, and provides a fully observable data model for convenient binding to the user interface (UI) layer. We built the Inbox app for Android directly on top of this Java data model.
The plot thickens
On the web, the story gets more interesting. We use the open sourced GWT cross compiler to translate the Java data model into JavaScript, which we build on for Inbox for the web. In recent years, GWT has made great strides in being able to output translated code which is conveniently and performantly accessed from native Javascript. For example the Reminder.snooze() method provided by the Java data model is exposed in exactly the same way in JavaScript.
For iOS we developed the now open source J2ObjC cross compiler to translate our Java data model to Objective-C, and again we get a natural API on which to build our native iOS Inbox app (complete with -[Reminder snooze]). The astute reader may wonder how we deal with the impedance mismatch when translating from a garbage collected language (Java) to a reference counted one (Objective-C). Generally, J2ObjC relies on Objective-C autorelease pools, so objects normally garbage-collected are instead freed when a pool drains. One problem with this approach is reference cycles; in places that cycles exist in our Java data model, we use a Java annotation to identify the @WeakReference. When transpiled, the corresponding property in Objective-C will have the __weak modifier, thus breaking the retain cycle. In practice we’ve found this to be a relatively minor problem and we have automation tests that flag the rare cases of new cycles creeping into the object model.
Conclusion
If you’re building an application that (a) has significant UI independent client logic, (b) is targeting multiple platforms, (c) must not compromise on user experience and polish, you now have a new option to consider: a shared, cross compiled data model powering fully native application UIs. This has worked well for Inbox, where we are sharing roughly two-thirds of our client code, and have delivered a product with the same functionality and ship date, without having to rewrite the entire thing three times. Want to learn more about the technologies that power Inbox? Check out http://gwtproject.org and http://j2objc.org.