With Kotlin 1.2 JetBrains introduced new experimental Kotlin Multiplatform projects which allow you to write common code once and target different JVM-based platforms such as Android, desktop, or even web. Kotlin/Native is another technology from JetBrains worth noticing. This is a technology for compiling Kotlin to native binaries without the need to use any VM. Kotlin\Native allows you to target iOS, macOS, and embedded systems.
Let’s put this together and create a cross-platform mobile app for Android and iOS with Kotlin! We will create an image browser for one of the most popular websites at NASA, Astronomy Picture of the Day.
Multiplatform projects have to be built with Gradle, other build systems are not supported.
There is a template for multiplatform projects in IntelliJ 2017.3 and Kotlin\Native plugin for AppCode 2018.1.1. However, I’ve used Android Studio for the Android and common code and Xcode for the iOS part.
These are regular Android and iOS apps. In the case of iOS, this is an Xcode project that uses the platform-ios module as a generic obj-c framework. In the case of Android, this is a conventional app with a dependency on the platform-android module.
This is a shared module and contains only Kotlin classes with no platform-specific dependencies. It can also contain interface\class declarations (without implementation) of platform-specific code. Such declarations allow using platform-dependent code in common modules and are marked with expected keywords. Platform-specific modules (platform-android and platform-ios) use the actual keyword in concrete implementations.
Common modules can only use Kotlin language with its common version of stdlib, kotlin-stdlib-common which seems to be quite limited.
Platform-android and platform-ios contain both the platform-dependent implementations of declarations in the common module for a specific platform and other platform-dependent code. Every single platform module is always an implementation of a single common module.
All of the projects except the regular iOS app were created in Android Studio using the project creation wizard. Next, I’ve manually edited gradle build files as follows:
apply plugin: ‘org.JetBrains.kotlin.platform.common’
dependencies { implementation “org.JetBrains.kotlin:kotlin-stdlib-common:$kotlin_version” }
kotlin { experimental { coroutines “enable” } }
Here we can see the only dependency is on kotlin-stdlib-common hence we are limited to it within this module. The second thing worth noticing is the coroutines “enable”. This statement is not needed to create a common module however I’ve decided to use another Kotlin’s experimental feature. Coroutines for asynchronous and non-blocking programming. I’ll provide a little bit more details in the chapters below but please keep in mind this is not the subject of this article.
apply plugin: ‘com.android.library’ apply plugin: ‘kotlin-platform-android’
dependencies { expectedBy project(“:common”) }
Here we can see that platform-android is a regular Android library (com.android.library) with the actual declarations mechanism (kotlin-platform-android). The expected statement tells the compiler where classes marked with expected keywords are declared.
dependencies { classpath “org.JetBrains.kotlin:kotlin-native-gradle-plugin:$kotlin_native_version” }
Here Kotlin/Native comes into play.
apply plugin: ‘Konan’
.. and it’s a plugin for Gradle. Konan allows compiling, building, and referencing libraries. In general, perform all actions needed to build an app.
konanArtifacts {
framework(‘AstronomyPictureOfTheDay’, targets: [‘iphone’, ‘iphone_sim’]) { enableMultiplatform true enableOptimizations true enableDebug true dumpParameters false measureTime false } }
dependencies { expectedBy project(‘:common’) }
Here we’ve got some Konan configuration section, defining the output framework’s name, targets, optimization, etc., and the same expected statement as in platform-android. In this case, we tell Konan to build an AstronomyPictureOfTheDay.framework for both iPhone and iPhone simulators with debugging and optimization enabled.
dependencies { implementation project(‘:platform-droid’)}
Nothing really special here, only dependency on platform-android.
iOS app is a little bit special as we cannot build it with Gradle. This is a regular XCode project with AstronomyPictureOfTheDay.framework included as an embedded library.
The dependency tree looks like this:
I’ve decided to use a contemporary `async\await like` approach to async programming across this cross-platform project. Unfortunately, Kotlin’s coroutines are not yet supported by Kotlin/Native and we need to introduce a common API with platform-specific implementations. We will use expect \ actual keywords here for the first time.
Here’s the common API: 3361b4c
Android-specific implementation: a0655fd
iOS-specific implementation: 7781417
For more about coroutines please visit:
https://youtu.be/_hfBv0a09Jc
Endpoint: https://api.nasa.gov/planetary/apod?date=[date]&hd=true&api_key=[api_key]
Response:
{ “date”: “2018-09-09”, “explanation”:”..”, “hdurl”:”https://apod.nasa.gov/apod/image/1809/CrabNebula_Hubble_3864.jpg”, “media_type”:”image”, “service_version”:”v1″, “title”:”M1: The Crab Nebula from Hubble”, “URL”:”https://apod.nasa.gov/apod/image/1809/CrabNebula_Hubble_960.jpg” }
Corresponding common data model: 2557424
Querying and parsing APOTD API is performed in platform-specific code with Retrofit\Gson on Android: 5033119 and Alamofire\SwiftyJSON: 47b922b, 70cbb4e
As you’ve probably noticed String was used to representing a date in ../apotd/dal/PictureOfTheDayRepository.kt. This is not the most convenient approach as we will have to add\remove days to get the next previous pictures. However, due to the limitations of Kotlin-stdlib-common, we have to create our common date representation with platform-specific implementations.
Common date representation: 2145ae2, Android-specific implementation based on JodaTime: c000d54, and iOS-specific implementation based on NSDate: 6e16950.
Let’s dig into the iOS-specific DateUtils class (6e16950) as it is quite unusual. Thanks to the import platform.Foundation.* we can use Apple’s Foundation framework within a module entirely written in Kotlin!
val dateComponents = NSDateComponents() dateComponents.setDay(dayOfMonth.toLong())
It looks weird, doesn’t it? 🙂 The same applies to UIKIt and any other iOS-specific frameworks. Here’s a little bit more about Kotlin/Native ↔Swift\Obj-C interoperability: https://kotlinlang.org/docs/reference/native/objc_interop.html
Here: 0f0b00f I’m updating repositories to our brand-new custom date representation so now we can add and remove days.
In 224f8de I’m introducing rather a simple presenter and view interface following the MVP pattern. The code is self-explanatory.
In e5363eb I’m integrating common logic into the Android client. The same integration is in f625a8a for iOS clients.
Kotlin Multiplatform + Kotlin\Native for mobile app development is no doubt an experimental feature. There’s still a long way until it’s production ready. Current IDEs are not prepared for multiplatform and the developer has to use several different IDEs to build the project and it leads to difficulties in debugging.
In contrast to Xamarin and Xamarin.Forms, Kotlin’s multiplatform project focuses on sharing only a core common module with business logic, leaving platform-specific code to platform-dependent modules. This approach has its pros i.e. it does not try to standardize platform dependant needs like I\O, and threading and gives the flexibility to use already existing platform-specific libraries and frameworks.
About the Authors
Project Manager
Business Development Manager
Leaware Founder
Business Development Manager
Marketing Specialist
Business Development Manager
Do you want to know how to
estimate the budget needed
for your project?