Dropbox developers have recently given some talks describing how they support both iOS and Android in their apps without having to recode everything on each platform. Let's review the reasons that led to their approach, the benefits it brings, and some key points learned through the process.
As Dropbox developers Stephen Poletto and Sean Beausoleil described in a talk given to Facebook Developers a few months ago, having different codebases for iOS and Android has some drawbacks:
- Development and maintenance costs double.
- Teams write and fix the same bugs multiple times.
- Bugs are reported for a specific platform and may go unnoticed on the other.
- Apps may exhibit subtle, undesired differences in behaviour.
- Performance optimization is expensive and very platform specific.
The cornerstone of Dropbox strategy to overcome all those issues is creating a shared C++ cross-platform library for all non UI code, e.g., data and network logic. The UI itself is coded natively to take full advantage of platform support for animations, device sensors, and to ensure full responsiveness.
The two developers described this approach as bottom-up, as opposed to a top-down approach favouring the use of a more abstract language, be it HTML 5 or JavaScript. Such approaches usually fail to deliver the expected performance, according to Poletto and Beausoleil.
On the other hand, C++ is well supported on iOS and can be easily mixed with Objective-C++ code. Furthermore, C++ can be used in Android apps through Android Native Development Kit (NDK), although not as easily as in iOS apps.
At UIKonf 2014, Mailbox app developer Steven Kabbes explained how Dropbox developers deal with the complexity of NDK development through the use of gyp, Google's meta-build system that can generate Xcode projects, Android and Unix makefiles, and Visual Studio projects from a JSON description. Steven has also published a GitHub project to showcase some of the cross platform techniques used at Dropbox.
C++ Layer Design
Dropbox C++ cross-platform layer has a simple architecture including:
- An SQLite database, playing the role of "source of truth".
- A Synchronization service running in its own thread responsible for keeping Dropbox server and the local SQLite DB in sync.
- An Operation Queue holding all user operations that have not yet been performed.
- An Operation Thread responsible to extract user operations from the queue and execute them against the Dropbox server.
The basic idea behind Dropbox C++ cross-platform layer is enforcing a strong boundary between it and the native code. This means, e.g., that the C++ and the UI layer do not share any data, and objects are copied back and forth across layers' boundary. The main advantage of this policy is that the two layers can be considered independent when it comes to concurrency, thus they do not require any specific cross-layer locking and can handle concurrency without regards for one another.
The main component in Dropbox C++ layer is an SQLite-powered query and persistence framework that allows Dropbox developers to not use Core Data on iOS. Such decision has not been driven by any issues with Core Data itself, which is fast and powerful, Kabbes says, but exclusively by the requirement of supporting Android, iOS, Mac, and in the future Windows. This component does not aim to be a full-fledged replacement for Core Data and just provides persistence and querying capabilities with the addition of what Kabbes consider a key component to guarantee UI responsiveness: a C++ notification mechanism similar to the one offered by NSManagedObjectContextObjectsDidChangeNotification
which allows to only send back and forth the delta changes. More details about this persistence component can be found in Kabbes' notes hosted on GitHub and in a post by Ole Begemann.
One hard decision when designing a C++ cross-platform layer is when to reimplement from scratch some functionality that either OS offers natively, and when to write a wrapper around it. You cannot reimplement the whole platform in C++, says Poletto. So, basic functionality like network access or SSL certificate validation is simply called back into the platform from the C++ layer. Ole Begemann offers a few more examples where reimplementing from scratch is simply not an option, e.g., background downloads through NSURLSession
, app backgrounding behaviour, and iCloud access. In other cases, functionality offered by platform-specific APIs can be reimplemented; such is the case with NSUserDefaults
, which has been replaced in Dropbox code by LevelDB.
Dealing with just one code base shared among iOS and Android platforms brings some benefits also, according to Poletto. First, there is more collaboration among the iOS and Android teams, almost by definition. Bugs are caught earlier and are fixed simultaneously for both platforms. Performance optimizations also benefit both platforms at the same time. Finally, Dropbox could leverage the Android beta testing program to test "iOS code," with the advantage of being able to push fixes immediately without waiting for the App Store review process.