It’s easy to get swept up with the notion of a fully cross platform mobile app, a truly write once for all platforms, Android, iOS, desktop, web, etc, however after working with a couple different technologies that offer this kind of development on a number of different projects we’ve found that not only did we keep running into the same issues but that the benefits weren’t exactly what we’d been led to believe they were.
Most cross platform technologies claim similar benefits with some of the most notable being speed of development due to one code base and needing only a single skilled team due to the shared language/technology used across all platforms.
Over the development lifetime of an app, we’ve observed that using a cross-platform shared UI technology didn’t actually save any time or cost when compared to using a multi-platform technology to share all business logic but with a native UI – it may even have cost more, on top of this to achieve a truly great UI/UX on each platform not only was an understanding of each platform essential but custom code is always needed to account for the differences in platform – one size does not always fit all.
The hidden cost
Cross-platform framework abstractions are complex, and as a result, they have bugs. These bugs can quickly negate any time saved in the initial development phases. They are, by their nature time consuming to understand, isolate and report. Often, either sub-optimal workarounds are required while waiting for the bug fix or worse still, the bug has to remain in production – sometimes this can be for many months or years! To have a truly single codebase mobile app, each platform must be compressed into their similar features which forces compromises on how the UI/UX can work on each platform. As much as each platform share a number of similarities they are not and never will be exactly the same Android != iOS != Desktop != Web, when attempting to accommodate all at once all experiences will suffer for it. Each of the technologies offer ways of getting around this by accessing the native layer in various ways to create custom native code, however, very quickly we found that we had to do this more and more negating any benefit we gained from sharing the code as the architecture becomes ever more complicated as a hybrid of different technologies/patterns end up being used.
The initial productivity benefits of a cross-platform shared UI framework are often over-hyped by those heavily invested in them; the other side of this coin, the total lifetime costs are harder to quantify and are less often discussed. We have a love-hate relationship with shared UI technologies – we marvel at how quickly the initial view can be created for both platforms and later hang our heads as we discover a bug in the framework and know the horrible experience that is about to unfold.
“The history of shared UI has a lot of pain and failure. The history of shared logic is the history of computers.”
At a crossroads
At the time of writing (mid-2021), it seems that we are at something of a crossroads regarding mobile app development technologies.
The leading cross-platform technologies, including Flutter, React Native, and Xamarin.Forms, are mature and have excellent developer communities, all of which however suffer to different degrees the issues mentioned above.
So what are the other options?
You could revert to the old ways and write a native app for each the platforms you want to support, this would allow you to avoid all the issues introduced by a cross-platform technology however you would also lose all of the benefits along with it.
Another option (and our favourite) is too try and get the best of both worlds, share as much code as you can across all platforms without compromising the native UI/UX of the platform.
Everything from the data layer all the way up the architecture to the presentation layer can and should be cross platform, this is normally most of your code! Understanding the domain problem and writing the domain/business/app logic code once for all platforms is probably the most significant benefit to any cross platform development. This code is usually the most timing consuming to create due to its domain complexities and presents a high proportion of the bugs during development/testing. By sharing the code up to the UI layer you gain almost all of the benefits of cross platform development, speed of development, cost reduction, single domain code base, single domain language to support, reduced testing requirements, etc..
There are of course downsides to this approach and the most notable of this is the need to create native UI layers for each platform you wish to support which does require an understanding of each platform and the technologies it uses, I would personally argue however that even when using a shared UI technology these requirements are still present, just hidden until something goes wrong on a certain platform.
Multi-platform code sharing below the UI layer is, we think, the best approach to writing mobile apps.
There are a number of different technologies you can use to create the architecture described above with one being using Xamarin.iOS and Xamarin.Android, using these allows the entire app to be written in C# including the native layers, sharing as much code as possible while still accessing the native API’s to create truly native UI/UX experiences getting the best of both worlds. Xamarin is not without its drawbacks of course with the main issue being that in order to access native frameworks binding libraries must be created and maintained which is not a pleasant experience.
The new kid on the block is Kotlin Multiplatform (KMP), with its mobile subset Kotlin Multiplatform Mobile (KMM). This technology is disruptive in that it promotes a Multi-platform approach using optional code-sharing across platforms alongside native code – but doesn’t prescribe any particular framework or provide a user interface. KMM libraries do offer a UI option and are available such as moko-widgets from IceRock, but this is not the main focus of the technology. By using KMM you are able to share code across platforms but the UI is truly native and up to the development team, this means that you can use any UI technology that suits your project and/or team but still get all the benefits of shared business logic. This is especially useful when considering the shift towards declarative UI’s on both iOS and Android. The declarative UI approaches offered by SwiftUI and Jetpack Compose are exciting new developments and look set to make creating native user interfaces a much more enjoyable experience. As KMM is not tied to a UI technology you are able to leverage developments such as this as they happen in the native world without having to worry about things like updates to libraries/bindings to happen before using them.
We acknowledge that no problem space or company is the same, and there is no correct answer other than ‘it depends’. For some situations, a shared UI framework will be the best solution by far, but we think a cross-platform core with a native UI is our defacto preferred option.
Our ideal architecture would be a KMM shared core comprising the domain/business logic and the network and data interactions. Sitting on top of this, a modern declarative UI leverages productivity gains and a more appealing development experience over the older native UI technologies.
This style facilitates the benefits of sharing the most difficult sections of the code and enabling a multi-functional team. Kotlin is a great language, popular with developers and is native to the Android platform.
Some skills are hard to find; at the time of writing, React Native skills appear to be in short supply in the UK, which may influence the technology choice above anything else.
Skills shortage is especially true for KMM, where it is a new technology. Finding developers with experience is challenging. Cross-training from Android or Xamarin may be a good option.
One of the biggest challenges for KMM is attracting iOS developers as the KMM code sharing is somewhat Android inspired due to the Kotlin language and Android Studio tooling.