Blog Infos
Author
Published
Topics
, , , ,
Published

Moving forward in the Migrating an Android app to iOS with KMP series, we are now focusing in creating a multiplatform user interface and thanks to Compose Multiplatform, migrating the code from Jetpack Compose is fairly simple, with a few caveats we will cover in this article.

Since there are a lot of details in each of the parts of the migration, I won’t deep dive on all of them. The migration code is shared in the sections below, but if you want to know more about specifics, let me know and we can follow up in an upcoming article.

This article is part of a series of migrating an existing Android app to run on iOS using Kotlin Multiplatform. You can access the other articles in the following links:

Architecture

To recap, let’s take a look at the application architecture after our last article, where we migrated all the data sources to KMP. We are close to have a full-fledged application running in both Android and iOS, we are only missing the user interface.

Alkaa’s simplified architecture

 

Compose multiplatform

The teams in Google and JetBrains are doing a great job in ensuring that the operability between Jetpack and Compose multiplatform works seamlessly. Almost every single Composable in the application simply worked when I migrate to multiplatform. We still need to handle the platform specifics such as notifications, permissions, resources, and soon-to-be-stable libraries such as navigation, ViewModel and Parcelable.

For simpler screens, the main changes during the migration were minimum, being mostly how to get resources such as strings and icons. Yes, even the imports stays the same (androidx.compose.*).

Codes comparison in GitHub to demonstrate the small changes

 

Resources

Speaking about resources, even though we don’t have an embedded way to get resources in the Compose Multiplatform framework yet, we have some great libraries from the community. The one used in Alkaa is the amazing IceRock’s Moko Resources.

After a quick setup, Moko Resources work very similar to the default Android resources, using .xml files and <string/> tags. The name convention and localization support is also similar, relying on in different folders with the locale code.

Moko Resources folder structure

 

With everything in place, using the resources is as simple as in Jetpack Compose, using the stringResource():

// Android
val string = MR.strings.category_default_personal.desc().toString(context)
// iOS
val string = MR.strings.category_default_personal.desc().localized()
view raw MyClass.kt hosted with ❤ by GitHub

Keep in mind that using resources out of the context of Compose, we would need to provide different implementations per platform since iOS and Android uses different parameters.

// Android
val string = MR.strings.category_default_personal.desc().toString(context)
// iOS
val string = MR.strings.category_default_personal.desc().localized()
view raw MyClass.kt hosted with ❤ by GitHub
Additional considerations during the setup

The code with the initial setup and module migration can be found here.

Native implementations

As expected, some parts of the code need to be implemented using native libraries, while we don’t have multiplatform alternatives. In the case of Alkaa the native implementations are mainly alarms, notifications and home screen widget.

The fun part of Kotlin Multiplatform is that even the Swift/Objective-C native code can be developed using Kotlin. Being an Android/Kotlin developer, this made my life much easier. Also, I need to give a lot of credit to GitHub Copilot that helped on this journey to create Kotlin code for iOS.

Here’s an example of native iOS implementation in Kotlin:

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

, ,

Writing Kotlin Multiplatform libraries that your iOS teammates are gonna love

Kotlin Multiplatform Mobile (KMM) is awesome for us Android Developers. Writing multiplatform code with it doesn’t diverge much from our usual routine, and now with Compose Multiplaform, we can write an entire iOS app without…
Watch Video

Writing Kotlin Multiplatform libraries that your iOS teammates are gonna love

André Oriani
Principal Software Engineer
Walmart

Writing Kotlin Multiplatform libraries that your iOS teammates are gonna love

André Oriani
Principal Software E ...
Walmart

Writing Kotlin Multiplatform libraries that your iOS teammates are gonna love

André Oriani
Principal Software Engine ...
Walmart

Jobs

override fun scheduleTaskNotification(task: Task, timeInMillis: Long) {
val content = UNMutableNotificationContent()
content.setBody(task.title)
content.setCategoryIdentifier(CATEGORY_IDENTIFIER_TASK)
content.setUserInfo(mapOf(USER_INFO_TASK_ID to task.id))
val nsDate = NSDate.dateWithTimeIntervalSince1970(timeInMillis / 1000.0)
val dateComponents = NSCalendar.currentCalendar.components(
NSCalendarUnitYear or NSCalendarUnitMonth or NSCalendarUnitDay
or NSCalendarUnitHour or NSCalendarUnitMinute,
fromDate = nsDate,
)
val trigger = UNCalendarNotificationTrigger.triggerWithDateMatchingComponents(
dateComponents = dateComponents,
repeats = false,
)
val request = UNNotificationRequest.requestWithIdentifier(
identifier = task.id.toString(),
content = content,
trigger = trigger,
)
NSLog("Scheduling notification for '${task.title}' at '$timeInMillis'")
val notificationCenter = UNUserNotificationCenter.currentNotificationCenter()
notificationCenter.addNotificationRequest(request) { error ->
if (error != null) {
NSLog("Error scheduling notification: $error")
}
}
}

Voyager follows a similar DSL approach to Koin which makes multimodule and encapsulation a breeze. The full migration from Jetpack to Voyager can be found here and here.

ViewModel

The ViewModel is another one of the Android libraries that are recently ported to Multiplatform, so I ended up using another Moko library called Moko MVVM. It not only adds support to ViewModel, but also LiveDataView Binding and Data Binding if you are still using it.

package com.escodro.task.presentation.detail.main
//...
import dev.icerock.moko.mvvm.viewmodel.ViewModel
internal class TaskViewModel : ViewModel() {
fun loadTaskInfo() {
// Everything an Android ViewModel has
viewModelScope.launch { doSuspendStuff() }
}
}
The migration was basically switching imports

The migration is straightforward, but platform-specific injections will be needed for Android and iOS.

Dynamic features

One interesting challenged I faced while migrating Alkaa was how to handle the on-demand delivery module. To summarize, in Android, we can set up modules to be downloaded on the fly during app execution to save app size. Even though iOS have a similar feature, I didn’t want to deep dive on it and decided to make it always available for the iOS variant. For my surprise, it was much easier than I thought.

Since KMP allows a different set of dependencies for each platform, it was easy to split them. For Android, we still use our “Split Install” library to download the dynamic module; for iOS we can directly depend on it, making it available right away.

//...
androidDependencies {
// Module responsible to download from Google Play
implementation(projects.libraries.splitInstall)
}
iosDependencies {
// Dynamic Module always available
implementation(projects.features.tracker)
}
//...

The full implementation can be found here.

Running the apps 🚀

As mentioned in previous articles, having a good modularization and separation of concerns allowed each layer and feature to be ported independently. For most part of the development, Alkaa had a few of features ported and two navigation graphs (Android and KMP) at the same time.

After porting all the features, we have an application that is very similar in the two platforms, with minimum platform-specific code. Keep in mind that Alkaa is a very simple application in features and scope, but gives a good idea on how much we can reuse as it is from the existing Android codebase.

Alkaa running in Android (left) and iOS (right)

 

Less than 1% of the code is Swift today
What’s next?

I am very happy that this series is getting outdated due to the amazing job that the Google developers are doing to port more AndroidX libraries to support multiplatform. Here’s a small recap on the libraries with KMP support:

If you don’t follow John, do yourself a favor. Also check his amazing GitHub repositories

As always, the full code is available in Alkaa’s repository and if you want to dive in the 97 commits that migrate the app from Jetpack to Compose Multiplatform, that’s the pull request:

https://github.com/igorescodro/alkaa/pull/597?source=post_page—–b5e01cc0769a——————————–

In the next article, we are covering how the pipeline was updated to generate both the Android and iOS apps and publish in the store automatically. I will also share some closing thoughts about this amazing journey. If there is something else you want me to cover, please leave a comment and I’ll do my best to address them. 😊

Thank you so much for reading my article! ❤️

This article is previously published on proandroiddev.com

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
Hi, today I come to you with a quick tip on how to update…
READ MORE
blog
Automation is a key point of Software Testing once it make possible to reproduce…
READ MORE
blog
Drag and Drop reordering in Recyclerview can be achieved with ItemTouchHelper (checkout implementation reference).…
READ MORE
Menu