Kotlin Native has been turning into a promising solution for easily sharing code between Android and iOS. Most cross platform solutions either expect you to write all your code in the target language such as Xamarin, or require a good bit of boilerplate for communication with the platform code such as react native. I am particularly excited for kotlin native because it simply outputs a framework that can be used seamlessly in an iOS project. This allows you to share platform-agnostic code like domain models and logic while keeping your ui code in the platform’s native language.

I decided to create a simple todo app using a redux-style architecture and firebase syncing. Here’s an example we made, over on our GitHub. The first step is to create a new android project in android studio, rename and ‘app’ module to ‘android’. Next, add a new module with the shared Kotlin code. You can use the Kotlin Native sample project as an example.

// build.gradle
buildscript {
    ext.kotlin_version = '1.2.0'
    repositories {
        google()
        jcenter()
        maven {
            url "https://dl.bintray.com/jetbrains/kotlin-native-dependencies"
        }
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "org.jetbrains.kotlin:kotlin-native-gradle-plugin:0.6"
        classpath 'com.android.tools.build:gradle:3.0.1'
    }
}

In the shared module, add

// shared/build.gradle
apply plugin: 'kotlin'
repositories {
    mavenCentral()
}
dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib"
}
apply plugin: 'konan'
konan.targets = ['iphone', 'iphone_sim']
def frameworkName = 'KotlinHello'
konanArtifacts {
    framework(frameworkName)
}

Adding the shared code as a dependency to the android module is trivial.

// android/build.gradle
dependencies {
    implementation project(':shared')
}

It’s a little more complicated for iOS, but not by much!

My first try was to run ./gradlew build to build the iOS framework, and then create a new iOS folder and generate a new project in it using Xcode. Then under the project window, General -> Embedded Binaries, add the framework from shared/build/konan/bin/iphone_sim/KotlinHello.framework, making sure to check "Copy items if needed". Now you can import it an a Swift file with and use it like any other framework.

Unfortunately there are two issues with the above approach: first, the above steps would need to be repeated every time you change the kotlin code, and second, the framework will only work on the iOS simulator. To fix this, add a couple of gradle tasks that use lipo to merge the iPhone and sim frameworks and copy those to the iOS project.

// shared/build.gradle
task lipo(type: Exec, dependsOn: 'build') {
    def frameworks = files(
            "$buildDir/konan/bin/iphone/${frameworkName}.framework/$frameworkName",
            "$buildDir/konan/bin/iphone_sim/${frameworkName}.framework/$frameworkName"
    )
    def output = file("$buildDir/konan/bin/iphone_universal/${frameworkName}.framework/$frameworkName")
    inputs.files frameworks
    outputs.file output
    executable = 'lipo'
    args = frameworks.files
    args += ['-create', '-output', output]
}
task copyFramework(type: Copy, dependsOn: lipo) {
    from("$buildDir/konan/bin/iphone") {
        include '*/Headers/*'
        include '*/Modules/*'
        include '*/Info.plist'
    }
    from "$buildDir/konan/bin/iphone_universal"
    into "${rootProject.rootDir}/ios"
}

Then under the project window, General -> Embedded Binaries, add the framework from ios/KotlinHello.framework. Finally, add a build phase to run the task with Project window, Build Phases -> New Run Script Phase

cd .. && ./gradlew shared:copyFramework

and drag it to above the “Link Binary With Libraries” phase.

Now Kotlin changes will automatically be picked up when you build the project.

And that’s it, now we have shared Kotlin code that can be used on both iOS and Android!

If you look at the sample, we can actually share quite a lot between platforms. All of our state, actions and reduces are in the shared Kotlin lib. For firebase, we needed to create an interface that was implemented on each platform to work with the native sdk: https://github.com/willowtreeapps/hello-shared-kotlin/blob/master/shared/src/main/kotlin/com/willowtreeapps/hellokotlin/Database.kt.

Roadblocks

While this is a promising start, there were a few issues we ran into that we’d want to figure out before using this in production:

  1. Generics seems limited when calling from swift. While you can see collection types (List, Map), any custom classes/interfaces have their generics erased. This leads to a lost of casting in the swift code.
  2. The lack of bitcode support. This means the framework cannot be used on a watchOS or tvOS project or with certain tooling like ixguard. This could also potentially be an issue in the future if Apple starts requiring bitcode on all iOS projects.
  3. Non-existent library support. There are very few multiplatform kotlin libraries so we’d be mostly creating wrappers to native interfaces for things like networking, persistence, etc. It would be nice to have these low-level building blocks in place.
  4. Kotlin Native is not 1.0 yet. We expect there to be breaking changes in upcoming releases before 1.0 hits which would require updates to our codebase.

All in all, this is looking to be a very promising solution for sharing code across platforms while continuing to provide a native experience. We will definitely be keeping an eye on it.