Building a Kotlin Native App on iOS and Android

In the following we will show you how to setup a basic iOS and Android Kotlin Native project to demonstrate the following functionalities:

  • View: Android and IOS shared code using Kotlin Native Common module
  • MVP: platform specific View implementation, common Presenter
  • Multi threading via Coroutines
  • HTTP via Ktor client
  • JSON serialization via kotlinx.serialization
  • Dependency Injection via Kodein
  • Logging via platform specific APIs

We will build a minimal working prototype to connect to a usplash API download a file and display the image. The goal will be to retain as much logic in the common (shared) module and rely on the native iOS and Android implementations to display the data.

Github Source

Overview

Common module can only contain Kotlin code and cannot depend on any JVM libs such as Retrofit, Moshi or RXJava

1. Project Structure

  • app/src/commonMainCommon: Kotlin code used by both iOS and Android
  • app/src/commonTest: Common Kotlin unit tests used by both iOS and Android
  • app/src/main/java: Main Android application. Kotlin and Java
  • app/src/test/java: Android unit tests. Kotlin and Java
  • iosApp: Xcode iOS Project. Swift and Objective C. Xcode will call the gradle task copyFramework part of build cycle and copy framework file
.
├── app
│ └── src
│ ├── commonMain
│ │ └── kotlin
│ │ └── sample
│ ├── commonTest
│ │ └── kotlin
│ │ └── sample
│ ├── iosMain
│ │ └── kotlin
│ │ └── sample
│ ├── iosTest
│ │ └── kotlin
│ │ └── sample
│ ├── main
│ │ ├── java
│ │ │ └── sample
│ │ └── res
│ │ ├── layout
│ │ └── values
│ └── test
│ └── java
│ └── sample
├── gradle
│ └── wrapper
└── iosApp
├── iosApp
│ └── Base.lproj
├── iosApp.xcodeproj
└── iosAppTests

2. Common: REST (KTOR)

The HTTP layer is responsible for connecting to remote REST web services to query the appropriate APIs and process responses. We will be using the Ktor HTTP client library provided by Jetbrains.

Gradle dependencies

Implementation

We will need to define an API service that will connect to the end point https://api.unsplash.com/photos/random and return the appropriate response. The endpoint requires parameter api key clientid. The method getRandom: PhotoResponse is prefixed with suspend keyword so is expected to be run within a coroutine context.

The install(JsonFeature) will automatically parse the response into the appropriate data class discussed later. JSON.nonstrict is an optional flag that tell the serializer to ignore any fields found in JSON response but not data class.

log() method is delegated to the appropriate platform specific logging framework (java.util.Log or NSLog)

3. Common: Serialization via kotlinx.serialization

Kotlin serialization consists of a multiplatform enabled compiler plugin and and runtime library to serialize JSON to Kotlin data classes.

Gradle

Implementation

The @Serializable annotation is a class level annotation that defines whether the class is serializable. @SerialName is an optional annotation if the JSON field and variable name will be different.

5. Common: Presenter

The presentation layer includes 2 components: Presenter and View. The presenter layer implements the MVP pattern and is responsible for updating the appropriate view on iOS/Android.

Contract Interfaces

The PhotoView will be implemented by the appropriate Activity on Android or ViewController on iOS. Each appropriate platform will implement the appropriate isUpdating and onUpdate callbacks and update the UI based on the platform best practices. The platform implementation will be covered later.

Presenter Implementation

The presenter’s responsibilities include managing coroutines, requesting and displaying data and handling errors. CoroutinePresenter will handle some basic responsibilities such as handling exceptions coroutine lifecycles. The presenter constructor requires a PhotoView and CoroutineContext.

6. Common: Dependency Injection

Dependency Injection is injected using the Kotlin service locator library Kodein. Since Kodein is only used in the common module a single dependency is required.

7. Common: Logging

Logged functionality is provided in the common module via the expect functionality and implemented in the platform specific logging APIs for Android and iOS. In the implementation below we define a method definition called log in the common module. When using the expect declaration the compiler will require corresponding actual declarations in the appropriate platform modules during compile.

Expect Declaration (Common Module)

Actual Declaration (iOS Module)

In the iOS module the corresponding implementation is delegated to the iOS library NSLog. Kotlin Native provides import headers for the iOS platform classes.

Actual Declaration (Android Module)

In the Android module the corresponding implementation is delegated to the Android library android.util.Log. The Android dependencies are included as dependencies part of the Android module in the corresponding build.gradle script.

8. Native: View (Swift and Kotlin)

Up until now all of the code has been written in Kotlin common module shared across both iOS and Android. The layer of the system that the user interacts with the UI will be written in the platform specific native frameworks and languages. The Android platform specific code will reside in a new Android module and the iOS code will reside in a Xcode project.

Android Implementation via Activity: PhotoView

The Android implementation includes an Activity that implements PhotoView and the associated methods: onUpdate, showError and the isUpdating boolean. The shared presenter, PhotoPresenter is implemented using a Kotlin lazy delegate. isUpdating is implemented using a Kotlin observable delegate. Whenever isUpdating is modified the appropriate progressBar will be shown or hidden. The image is displayed using the Android image processing library, Glide.

iOS Implementation via ViewController: PhotoView

The Kotlin common code is compiled into a framework file that is imported into Xcode and available in the app module. The Swift implementation utilizes a ViewController which imports the Kotlin interface PhotoView and utilizes the Kotlin PhotoPresenter. The ViewController implements the same methods as Android implementation: isUpdating, showError and onUpdate. When the screen loads the view controller will request the start of data loading via actions.onRequestData. The image is rendered using the UIKit library, UIImage.

iOS Coroutine Context

Currently Kotlin/Native does not support communication between threads using coroutine primitives. Kotlin Native also provides the Worker abstraction.

For simplicity in the following implementation we create a single threaded dispatcher.

Wrapping Up

In the following example we showed that a basic application can be developed for iOS and Android applications with the majority of code shared across both iOS and Android. The layer of the app that the user interacts with (UI, notifications etc) will be developed natively. Kotlin Native is a promising technology that does not introduce additional frameworks, libraries or friction and promotes seamless sharing.