This article was originally written in Japanese by my colleague 久保出雅俊 . You can find the original article at https://www.wantedly.com/companies/wantedly/post_articles/282562
In this article, we are going to talk about the reasoning behind our decision to adopt, and move away from React Native, and why we decided to adopt Kotlin Multiplatform in our mobile apps.
Why we adopted React Native in the first place
In 2018, we decided to do a full rewrite of our iOS application. This required a huge amount of effort and time that involves almost everybody in the mobile app team.
In parallel with the iOS app rewrite, we also wanted to introduce a new feature called “Discover”, where users can find various interesting contents inside our platform. However, since most of the mobile engineers were focusing on rewriting the application, we decided to introduce React Native to allow web engineers to work on this feature separately. “Discover” feature was initially introduced in the old version of our iOS app and our data showed that it performed admirably, hence we decided to bring it into the new iOS app as well.
Subsequently, we introduced “Discover” to our Android app, and our apps’ architecture roughly looked like this:
Our issues with React Native
This list of issues were issues that are specific to us and do not represent issues with React Native as a whole.
Lack of active maintainers
Once the “Discover” feature was stable, the web engineers that were originally involved with the React Native development went back to work on the web platform, and nobody was actively maintaining the React Native repository.
Updating Xcode or other build tools became an issue because most of the time we need to update React Native toolings first. These updates sometimes were not straightforward and ended up costing us a lot more effort than necessary. This process of updating the build tools quickly became a major pain point for mobile engineers.
Differences in technology
Even when the mobile engineers decided to maintain the React Native repository themselves, they lack the skill and knowledge of the inner workings of React Native, and learning these skills is not cheap.
Also with the subsequent introduction of React Native component into our Android app, it’s hard to work with React Native without the knowledge of both iOS and Android platform, as each OS works differently.
Discrepancies in the UI/UX
There were some elements that only work on the iOS platform, and when we decided to integrate “Discover” feature into our Android app, we had to add a condition to hide or remove said elements. This created a discrepancy of feature between both apps.
There were also some minor differences between native UI elements and React Native UI elements. Hence, UI elements that exist in both native and React Native might look similar but have some disparity (e.g., touch feedback on touchable elements in Android app).
With the increase of build tools around the app, build time became excessively slow and complicated.
Removing React Native
For quite some time, these issues remained unsolved. But recently, we got an opportunity to work on the React Native component itself, thus we decided to also evaluate the current implementation and plan the future of our app. While it is true that React Native has really nice features such as hot reload and easy to implement UI framework, we think that our current issues outweigh its benefits and potential, hence, we decided to quit React Native altogether.
With regards to the engineers’ productivity of the mobile platform as a whole, duplicate implementations (like network API calls) that are required on both iOS and Android are very costly, and sometimes, can have subtle differences. We wanted to tackle these issues and we thought cross-platform technology might be a good fit, so we decided to evaluate Kotlin Multiplatform and introduce it into our app.
Kotlin Multiplatform (Kotlin MPP), is a Kotlin language feature that allows you to share code that is written in Kotlin between multiple platforms.
- Code that is written using Common Kotlin will be recompiled to native code on each target platform.
- Allows you to write native code of each platform in Kotlin.
- Access platform-specific APIs using the expected/actual declarations.
Kotlin Multiplatform Mobile (KMM) is an official site that showcases usages of MPP for mobile applications, and in it, there’s an example on how we can make use of MPP to share business logic between different platforms but still makes use of the native UI elements. We decided to follow this example and implement our MPP architecture as such.
In our iOS App, we make use of ReactorKit, a flux-like Swift application framework. As for Android, we also make use of a similar flux-like framework that we built internally with Android’s ViewModel. Naturally, in the case of our MPP implementation, we wanted to adopt a similar model with ReactorKit and flux-like architecture in an effort to reduce the cost of learning and implementing a new architecture.
To achieve that we make use of the following libraries:
- Kotlinx Coroutines.
- Ktor Client.
- Kotlinx Serialization.
- SQLDelight for application’s single source of truth.
… and a few other libraries. It has a remarkably similar architecture to KaMPKit implementation that is showcased inside KMM.
Mobile app architecture
At the time of this writing, we just finished removing React Native component from our Android app and this is the current architecture of both of the apps.
With the implementation of MPP and native UI element, we saw a tremendous PV improvement of 46% on one of our screen that was originally implemented with a WebView. Furthermore, it has a 50% smaller app download size.
iOS adoption of MPP is currently underway, and in the future, we hope to broaden the application of MPP to improve the overall productivity of all mobile engineers in the company.
Pros and cons of MPP
Here are the pros and cons that we encountered when working with the current iteration of MPP.
- From the point of view of an Android app, MPP module is just like another Kotlin module, and introducing an MPP module to an existing Android project was very smooth. We didn’t notice any difference when moving the business logic from Android to MPP.
- When the interface of an MPP component has been decided, implementing UI in Android and business logic in MPP can be done in parallel by 2 different engineers.
- Since there is no “UI” element in MPP, you are forced to write unit tests to validate your code. This naturally yields a high code coverage.
- MPP is still an experimental feature. It works, but you still have to take account of that fact.
- Different memory management model in iOS (Kotlin/Native). In Kotlin/Native code, there’s a concept of immutability that does not apply to Kotlin/JVM code. When working with coroutines, you’ll often encounter
InvalidMutabilityExceptionif you’re not careful. There are plans to change this model, but in the meantime, you have to be careful of which thread your code is invoked in.
- From the point of view of an iOS app, MPP doesn’t look very different from React Native implementation. However, UI components, which usually requires a deep knowledge of each platform to implement, will not be shared and you won’t expect differences as drastic as React Native. There’s also an official guide on how to prepare your team for MPP, as long as you follow this guide, you should be able to create a component that is easy to maintain for all engineers.
We have talked about why we adopted and quit React Native implementation in our mobile app, and also why we decided to adopt Kotlin Multiplatform. There is still some work to be done to remove React Native completely from our apps, but we hope that with our experience and learnings with React Native, we can continue to examine different new cross-platform technologies, such as MPP.