Xamarin

Kotlin Multiplatform Projects + Kotlin/Native = upcoming alternative to Xamarin?

In recent years, when the popularity of mobile applications has grown, the frameworks that allow building multiplatform applications, i.e. writing shared code for several mobile platforms are quite popular. If you are interested in programming multiplatform applications and you want to keep up with the latest technology so, this article may interest you.

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 a need to use any VM. Kotlin\Native allows you to target iOS, macOS and embedded systems.

Let's put this together and create 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.

Build system and IDE

Multiplatform projects have to be built with Gradle, other build systems are not supported.

There is a template for multiplatform project 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 iOS part.

Project structure

ss

 

Android\iOS

These are regular Android and iOS apps. In case of iOS this is an Xcode project that uses platform-ios module as a generic obj-c framework. In case of Android this is conventional app with dependency on platform-android module.

Common

This is 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 module and are marked with expect keyword. Platform specific modules (platform-android and platform-ios) use the actual keyword in concrete implementations.

Common modules can only use Kotlin language with it’s common version of stdlib, kotlin-stdlib-common which seems to be quite limited.

Platform-android\ios

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.

 

Project creation

All of the projects except the regular iOS app were created in Android Studio using project creation wizard. Next I’ve manually edited gradle build files as follows:

Common
/common/build.gradle
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 coroutines "enable". This statement is not needed to create common module however I’ve decided to use another Kotlin’s experimental feature. Coroutines for the asynchronous and non-blocking programming. I’ll provide a little bit more details in chapters below but please keep in mind this is not the subject to this article.

Platform-android
/platform-android/build.gradle
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 expectedBy statement tells the compiler where classes marked with expect keyword are declared.

Platform-ios
/platform-ios/build.gradle
dependencies {
  classpath "org.jetbrains.kotlin:kotlin-native-gradle-plugin:$kotlin_native_version"
}

 

Here Kotlin/Native comes into play.

apply plugin: 'konan'

.. and it’s plugin for Gradle. Konan allows to compile, build and reference 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 expectedBy statement as in platform-android. In this case we tell Konan to build an AstronomyPictureOfTheDay.framework for both iPhone and iPhone simulator with debugging and optimization enabled.

Android
/droid/build.gradle
dependencies {
  implementation project(':platform-droid')
}

Nothing really special here, only dependency on platform-android.

iOS

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 a embedded library.

Actually dependency tree looks like this:Brak alternatywnego tekstu dla tego zdjęcia

Coroutines

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 common API: 3361b4c

Android specific implementation: a0655fd

iOS specific implementation: 7781417

For more  about coroutines please visit: https://youtu.be/_hfBv0a09Jc

 

Data Access Layer

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

Date representation

As you’ve probably noticed String was used to represent a date in ../apotd/dal/PictureOfTheDayRepository.kt. This is not the most convenient approach as we will have to add\remove days to get next\previous pictures. However due to limitations of kotlin-stdlib-common we have to create our own 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 are able to 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 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 are able to add and remove days.

Presenter and View (basic MVP)

In 224f8de I’m introducing rather simple presenter and view interface following MVP pattern. The code is self explanatory.

Tying this all together

In e5363eb I’m integrating common logic into Android client. The same integration in f625a8a for iOS client.

Brak alternatywnego tekstu dla tego zdjęcia

Summary

Kotlin Multiplatform + Kotlin\Native for mobile app development is no doubtly an experimental feature. There’s still a long way until it’s producton ready. Current IDEs are not prepared for multiplatform and 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 it’s pros i.e it does not try to standarise platform dependant needs like I\O, threading and gives a flexibility to use already existing platform specific libraries and frameworks.




CONTACT US

Do you have a different question?

You did’t find the answer to the question bothering you? Write to us! We can help!

Leaware SA, with its registered office at 86/1 Perkuna, in Warsaw, is the controller of your personal data.

Similar posts

Case Study

How to win RemaDays Prize in 120 days?

4 turning points in the process of building start-up application for Vector24 – how thanks to LEAN methodology and code-sharing technology we were able to create an ideal, prize-winning MVP.

Leaware

Backend

ASP.NET Core - many contexts, many databases

Implementation of the first, working version of the system usually does not involve any major problems - the programmers receive previously prepared documentation, adapt their development environments and start working. After x-hours of development, y-hours of testing and z-hours of conversations with clients, the implementation department receives a green light - we start to implement the service on the production server. Everything went according to plan, the application works flawlessly, and we count the next unique users using the service. Unfortunately, one element was not foreseen - the exponentially growing size of the database.

Michał Bethke

Case Study

Insurtechs – How to take a bite of a cake worth billions of euros?

The financial industry, like most segments of the market, has undergone a decade of digital transformation. The result is the creation of increasingly powerful financial firms operating in the financial sector, called ‘fintechs’. Their strategy is to use state-of-the-art technology from product development, sales, customer contact, and consumer relationships.

Zbigniew Waz

ASK US

Let's stay in touch!

If you want to know more about how we can help you or you like to be on a current basis, please leave us your e-mail.

Leaware SA, with its registered office at 86/1 Perkuna, in Warsaw, is the controller of your personal data.