lunes, 8 de mayo de 2017

How to (not) reuse code between Android and iOS

Most of the mobile applications we build these days have to work in two different platforms (Android and iOS).  Each of these platforms has its own frameworks, tools and programming languages so usually you end up building two completely separated applications and many times even built by separate teams.

[Note: If you are using some cross-platform development environment like react-native or Xamarin or building a Web/Hybrid app you are "lucky" and this post doesn't apply to you :)]

Unless you are working in a very simple app at some point you will realize that there are some parts of the application that you are implementing twice because you need it in both platforms (for example some business logic or the code to make requests to the HTTP server APIs).

Based on the capabilities of Android and iOS you have basically two options:
Option 1: Implement everything twice using the official language and libraries of each platform (f.e. implement the access to HTTP APIs using Swift and URLSession in the iOS app and using Java and Volley in the Android app)
Option 2: Implement the reusable code in C++ and compile it in the iOS app (creating a Objective C++ wrapper) and use it in the Android app (creating a JNI wrapper).

These are some possible advantages of Option 1:
  • Code is usually easier to read and maintain when written in modern languages (for example Swift vs C++).
  • Native integration: When using an Android library to make HTTP requests it will be probably integrated with the system proxy configuration and validates the SSL certificates with the system CAs by default.
  • No plumbing/boring code to write to provide access to the C++ library from the application (for example with JNI).  This can be partially mitigated using frameworks like SWIG to autogenerate the wrappers but it is still boring and usually problematic.
  • Simpler to debug because there is a single layer instead of having to make calls accross layers with different technologies(for example with JNI).
  • Build process faster and simpler because of less libraries/tools (for example no ndk required)
These are some possible advantages of Option 2:
  • No duplicated code to develop and maintain.
  • Avoid inconsistencies in naming, algorithms, protocols implementation because it is implemented in a single place.
  • Performance can be better.  Almost this is not an issue in most of the cases.
As we can see there are important pros and cons of both options so let's try another approach....  Let's check what are other popular mobile libraries doing?

I put some of those libraries in a diagram across two axis: Y for size/complexity of the library and X for number of platforms to support.  Other relevant variable could be how relevant is the performance optimisation but I don't want to make a 3D diagram :)  

In blue libraries using Option 1 and In green libraries using Option 2
[Apology: I picked some popular libraries I have used in the past and the lines of code and number of platforms is just an estimation, I didn't really count them]

As we can see most of the popular libraries are using Option 1 reimplementing the library twice, once for Android and once for iOS.  On the other side some big libraries related to real time communications or databases are using Option 2 implementing the core in C++ and exposing it with wrappers to Java and Objective-C applications.


What is the right solution probably depends on the type of project and the team building it but in my opinion in many (or most) of the cases it is less effort to develop and maintain 2 simple implementations than writing and maintaining a single more complex implementation plus the wrappers to different platforms.   In addition you can (should) mitigate the issues of Option 1 making use of tools to autogenerate code when possible, for example using protocol buffers/grpc for the client-server communication or swagger to generate clients for REST APIs.

I'm very interested on knowing your opinion on this topic, What do you think?   What are you doing right now in your projects?

2 comentarios:

  1. If it were just the two mobile platforms being targeted, I'm not convinced it's ever appropriate to maintain a portable library, even when pairing up with a multi-targeted beast like WebRTC. We've seen it takes at least 1 full time job just to keep the project afloat, let alone moving forward at a respectable clip.

    In my opinion, the crucial decision influencer is not the first two libraries that you target, but rather the following three (or more). Android's Java and iOS's ObjC/Swift are sufficiently different that quality of life is better in most cases to maintain separately, but when you add Linux C++, OS X ObjC (admittedly nearly a freebie if targeting iOS simultaneously), various Windowses in any language, and maybe toss a NodeJS binding in there for fun, then the costs of portability maintenance start to really earn their keep.

    Notice that the ambition / complexity of those products in your graph seem to go up right along the (platforms targeted * LoC) equation. I posit that writing a few function bindings over a web API *in most cases* will dwarf in comparison to the overhead of maintaining a project that requires use of tricky networking and graphics processing.

  2. I kind of agree, but in general I would start with 2 separate implementations (android and iOS) and then if/when you need all those "desktop platforms" do the C++ implementation and decide if you want to reuse it for Android and iOS (I'm tempted to say no and maintain "just" 3 implementations unless it is more than 10K LoC :)).

    In addition you have the JS version that you almost need for sure if you have a web client and that would be the NodeJS version automatically and easier to maintain than a NodeJS wrapper IMO.