The (not so) hidden cost of sharing code between iOS and Android

// By Eyal Guthmann • Aug 14, 2019

Until very recently, Dropbox had a technical strategy on mobile of sharing code between iOS and Android via C++. The idea behind this strategy was simple—write the code once in C++ instead of twice in Java and Objective C. We adopted this C++ strategy back in 2013, when our mobile engineering team was relatively small and needed to support a fast growing mobile roadmap. We needed to find a way to leverage this small team to quickly ship lots of code on both Android and iOS.

We have now completely backed off from this strategy in favor of using each platforms’ native languages (primarily Swift and Kotlin, which didn’t exist when we started out). This decision was due to the (not so) hidden cost associated with code sharing. Here are some of the things we learned as a company on what it costs to effectively share code. And they all stem from the same basic issue:

By writing code in a non-standard fashion, we took on overhead that we would have not had to worry about had we stayed with the widely used platform defaults. This overhead ended up being more expensive than just writing the code twice.

Before breaking down all the different types of overhead we encountered, I’d like to clarify that we never actually got to a point where most of our codebase was developed in C++. The overhead of C++ adoption actually prevented us from ever moving fully in this direction.

It’s also worth noting that much larger companies like Google and Facebook have been developing scalable code-sharing solutions for several years. These solution have, so far, gained only limited adoption. While you can avoid some of the overhead described below by leveraging a 3rd party code sharing solution like React Native or Flutter, some will still apply (at least until one of these technologies gains traction and mature). For example, Airbnb sunset their use of React Native for many of the same reasons outlined in this post.

We can group the different types of overhead we faced into four main categories:

The overhead of custom frameworks and libraries

The easiest overhead to predict with C++ is the need to build frameworks and libraries. This roughly breaks down into 2 subcategories:

  • Frameworks that would allow us to interact with the host environment to build a full fledged mobile app. For example:
    • Djinni, a tool for generating cross-language type declarations and interface bindings
    • A framework for running tasks in the background vs the main thread (a trivial task to perform in platform native languages).
  • Libraries that would replace language defaults/open source standards that we could have used within the platform native languages. For example:
    • json11 for JSON (de)serialization
    • nn, non-nullable pointers for C++.

None of this code would have been necessary had we stayed with the platform native languages, and our contributions to open source projects would have probably benefited more developers if they were in platform native languages. It’s possible we could have done a better job at leveraging open source C++ libraries, but the open source culture in the C++ development community was (is still?) not as strong as it is in the mobile development community (and in particular in the almost non-existent C++ mobile community).

Note that these costs are particularly high in C++ (as opposed to other possible non-native languages like Python or C#) because it lacks of a single, full-featured standard library. That being said, C/C++ are the only languages with a compiler supported by both Google and Apple, so using a different language would have created a whole host of other problems to deal with.

The overhead of a custom development environment

The mobile ecosystem has a lot of tooling available to make development more efficient. Mobile IDEs are very rich and Google and Apple have invested a lot of resources in making them the best development experience for developers on their corresponding platforms. By moving away from the platforms’ defaults we gave away some of these benefits. Most notably, the debugging experience in a platform’s native language is generally superior to debugging in C++ code via the platform’s default IDE.

One particularly memorable example is a bug that was causing a deadlock in our background threading framework leading the app to randomly crash. These types of bugs are hard to pin down even when you work on a simple, standard stack. Because this issue involved debugging multi-threaded code running back and forth between C++ and Java it took weeks to nail down!

In addition to losing tools, we also had to invest time in building tools that would support C++ code sharing. Most importantly, we needed a custom build system that created libraries containing C++ code as well as Java and Objective-C wrappers and could generate targets that were understood by both Xcodebuild and Gradle. This system was a big drag on our resources as it needed to be constantly updated to support changes in two build systems.

The overhead of addressing differences between the platforms

Even though both iOS and Android apps are “mobile apps” that generally expose the same features and functionality, the platforms themselves have some differences that affect implementation. For example, the way that an application can execute background tasks on each platform is different. Even things that started fairly similar when we adopted this cross-platform strategy have greatly diverged over time (e.g., interaction with the camera roll).

As a result, you can’t even really write the code once and have it run on the different platform out of the box. You have to spend a lot of time integrating the code into the different platforms and writing platform specific code (and sometime that code ends up in the C++ layer itself!).

This makes the theoretical benefit of only writing the code once not live up to the promise, thus greatly reducing the benefits of this approach to begin with.

The overhead of training, hiring, and retaining developers

Last, but definitely not least, is the cost of training and/or hiring developers to work on our very custom stack. When Dropbox started with this mobile strategy, we had a core group of experienced C++ developers. This group started the C++ project and trained other mobile developers at Dropbox on how to contribute to the codebase.

Over time, these developers moved on to other teams and other companies. The engineers who remained did not have sufficient experience to fill the technical leadership gap that opened up, and it became increasingly difficult to hire replacement senior engineers with relevant C++ experience who would be interested in mobile development.

As a result, we ended up with a real lack of critical expertise to maintain the C++ codebase. The only way to restore this expertise was to invest substantially in one of two options:

  1. Find and hire candidates with this very specific skillset (we tried to hire for this role for over a year with no success)
  2. Train mobile (or C++) engineers in-house with the missing skillset, which is practically impossible to do when you no longer have someone with the desired skillset to perform the training. Even before the core group moved on, mobile engineers were generally not interested in learning C++, so finding people to train was a big issue, as well

On top of the hiring problem, rolling our own tech stack created a retention problem—mobile developers simply did not want to work on a C++ project. This caused a lot of talented mobile engineers to leave the project rather than slog through a not-very-well maintained custom stack. In general, the mobile development community is very dynamic—new technologies and patterns emerge frequently and are adopted quickly. The best developers like to keep their skills up to date.

Keeping up with the latest and greatest is a challenge in a mature product environment with a standard stack. You sacrifice adoption speed for stability. This challenge is hugely magnified when you lock yourself into a custom stack and out of the wider mobile ecosystem.

Conclusion

Although writing code once sounds like a great bargain, the associated overhead made the cost of this approach outweigh the benefits (which turned out to be smaller than expected anyway). In the end we no longer share mobile code via C++ (or any other non-standard way) and instead write code in the platform native languages.

In addition we want our engineers to have a delightful experience and to be able to contribute back to the community. This is why we made the decision to align our practices with industry standards.

We’re Hiring! If you are an Android or iOS engineer who gets excited about building amazing products and contributing to the mobile ecosystem, come join the team!


// Copy link