Mobile App Development Using Kotlin Multiplatform Mobile (KMM)
Introduction
Hello, we're Yao Xie and Mooseok Bahng, who work on mobile app development in the Global Group at KINTO Technologies.
We're currently working on an app called Global KINTO App. The Global KINTO App (GKA) was built with the goal of connecting together KINTO services all over the world with a single app. So far, KINTO services have been deployed in Thailand and Qatar.
While working on a project destined to replace an existing app, we decided to adopt Kotlin Multiplatform Mobile (KMM). So, we're going to talk about that.
Background on why we decided to adopt KMM
There were a few issues that lead to our decision to adopt KMM. - No matter what we did, differences always arose in the business logic between iOS and Android. - The development team is physically split between 2 locations, which can impact development efficiency. -> We thought we could improve things by dividing the team into KMM and native. - Our development resources are limited, so we want to create an efficient development system.
Based on these, we started to consider KMM.
What is Kotlin Multiplatform Mobile (KMM)?
An SDK for developing iOS and Android apps, KMM uses Kotlin as its base language, and offers the benefits of both cross-platform and native apps. You can develop a common business logic using KMM, then develop the platform-dependent UI elements natively.
We personally think that it's best to provide the optimal UI/UX for each OS. With KMM, you basically develop each UI natively, so the UI/UX can be optimized with little dependence on iOS and Android, and we also think version upgrades are going to have virtually no impact.
KMM is still a new technology and isn't very mature, but it's been getting used more and more by various companies in recent years.
Kotlin Multiplatform Mobile (source: https://kotlinlang.org/lp/mobile)
Architecture
Before we talk about KMM, let's talk about the architecture currently being used by the development team. In short, we develop using an MVVM pattern. This policy basically won't change even though we're adopting KMM.
At this point, we were wondering just how much we should include in KMM. There are around three options.
KMM | Native | |
---|---|---|
Option 1 | Repository, Usecase, View Model | UI |
Option 2 | Repository, Usecase | View Model, UI |
Option 3 | Repository | Usecase, View Model, UI |
We tried various approaches, but for now, we're moving in the direction of using KMM up to the view model. We also considered leaving out the view model, but couldn't find any good reasons for handling it separately despite making the effort of adopting KMM. This is especially true for simple features like displaying data in a list. Maybe we'll need to do the view model separately as more complex features get added. When that happens, it should be possible to do just some parts of it separately.
The codebase for iOS is now pretty compact. We're using KMM up to the domain layer and view model, so there's only UI- and platform-dependent hardware-related features, and we think it'll probably amount to at most half as much source code as before.
Here's some iOS code for a simple screen with an FAQ list. Apart from a common UI Utility class, this is all we need.
swift struct FaqView: View {
private let viewModel = FaqViewModel()
@State var state: FaqContractState
init() {
state = viewModel.createInitialState()
}
var body: some View {
NavigationView {
listView()
}
.onAppear {
viewModel.uiState.collect(collector: Collector<FaqContractState> {
self.state = $0
}
) { possibleError in
print("finished with possible error")
}
}
}
private func listView() -> AnyView {
manageResourceState(
resourceState: state.uiState,
successView: { data in
guard let list = data as? [Faq] else {
return AnyView(Text("error"))
}
return AnyView(
List {
ForEach(list, id: \.self) { item in
Text(item.description)
}
}
)
},
onTryAgain: {
viewModel.setEvent(event: FaqContractEvent.Retry())
},
onCheckAgain: {
viewModel.setEvent(event: FaqContractEvent.Retry())
}
)
}
}
Pros
- Single codebase
We can manage iOS and Android networking, data storage, business logic, and more with a single codebase.
- Consistency
Having a common business logic means we can basically provide the same UX.
- Efficiency
Adopting KMM has enabled us to do development work more efficiently. Cutting our time costs almost in half means we're getting to spend that much longer on source code optimization and business deployment.
- Expandability
We can easily expand development as needed to include other platforms besides iOS and Android.
Cons
- For iOS debugging, you need to install a separate plugin.
- If you use XCframework, using Simulator on an Apple Silicon Mac results in an error because it references arm64. We think this will need to be fixed on the KMM SDK side, but for the time being, we can use Simulator either by adding arm64 to the excluded architecture, or running Xcode in Rosetta mode.
Distribution method for iOS
Build & Sourcesets
Build XCFrameworks
Up to now, distributing KMM to iOS has basically been done with a Universal (FAT) framework. Official support for XCFramework has finally come out recently, so we're planning to go with that.
Other
They're not directly related to KMM, but here are some other new technologies we're also thinking of adopting:
Ktor
- You can use the same settings for both clients.
- The API Request code is the same.
- The engines for iOS and Android are separate, but no additional code is required.
Apollo Client
We're also using some of the GraphQL API in some of existing projects. We're thinking of adopting the Apollo Client in order to use GraphQL. You just need to create Queries.graphql using schema.graphqls in the backend, and Models, Adapters, and Queries will all get created automatically.
MMKV
MMKV is a mobile key-value storage framework. It supports multiple platforms, including Android and iOS of course.
https://github.com/Tencent/MMKV https://github.com/ctripcorp/mmkv-kotlin
With MMKV-Kotlin, we can easily integrate MMKV into our projects and manage key-value storage with a shared module.
Performance comparison on Android
Performance comparison on iOS
Future developments
Fast growing KMM ecosystems
KMM is being developed by Jetbrains. It can be used seamlessly with Android Studio and has some Xcode support as well. It's spreading among developers, and there are a lot of open source libraries for it.
Cross-platform UI
Touchlab has already started experimenting with using Compose UI for both iOS and Android.
@Composable
internal actual fun PlatformSpecificSettingsView(viewModel: SettingsViewModel) {
IconTextSwitchRow(
text = "Use compose for iOS",
image = Icons.Default.Aod,
checked = viewModel.observeUseCompose,
)
Divider()
}
KMM may start to officially support cross-platform UIs in the near future.
Summary
In this article, we talked about how we've adopted KMM. We're now expecting it to bring the following improvements: - Being able to minimize the business logic gap between iOS and Android - Being able to optimize the development team structure if required - Reducing the development time to a certain extent
We're still in the early stages of adopting KMM and are going to face lots of issues and steadily build up our know-how, so we'll share more once we've made some more progress.
Thank you for reading.
関連記事 | Related Posts
We are hiring!
【プロジェクトマネージャー】モバイルアプリ開発G/大阪
モバイルアプリ開発GについてKINTOテクノロジーズにおける、モバイルアプリ開発のスペシャリストが集まっているグループです。KINTOやmy routeなどのサービスを開発・運用しているグループと協調しながら品質の高いモバイルアプリを開発し、サービスの発展に貢献する事を目標としています。
【iOS/Androidエンジニア】モバイルアプリ開発G/東京
モバイルアプリ開発GについてKINTOテクノロジーズにおける、モバイルアプリ開発のスペシャリストが集まっているグループです。KINTOやmy routeなどのサービスを開発・運用しているグループと協調しながら品質の高いモバイルアプリを開発し、サービスの発展に貢献する事を目標としています。