Kotlin Multiplatform

Getting started with Kotlin Multiplatform


Introduction

While building apps for iOS and android we often end up writing the same business logic for both the platform. After building couple of mobile apps, this question might have crossed your mind: Why I can’t reuse the same business logic on both the platform? This guide will help you getting started with Kotlin Multiplatform.

Kotlin Multiplatform allows you to write common code that can be shared between Android and iOS. Sharing code between mobile platforms is one of the major Kotlin Multiplatform use cases. It is now possible to build mobile applications with parts of the code, such as business logic, connectivity, and more, shared between Android and iOS.

In this tutorial, we will use Android studio to create our multiplatform project.

Getting Started with Kotlin Multiplatform

Launch your Android studio, create a new project. For this tutorial we are selecting Empty Activity template. Make sure you select the language as Kotlin.

Let’s wait for Gradle to finish its syncing. Once Gradle is done with its task, let’s create our Shared code module.

Right click on the app directory and go to New ➔ Module. Select Java or Kotlin Library

getting started with kotlin project
Select a Module Type

Click Next and give the name to your module (I used Shared as my module name). You can now see the newly created Shared module in your Project window.

Let’s do some quick changes to the Shared module folder Structure. Make sure your module folder structure looks similar to this:

kotlin multiplatform shared files structure
Shared module folder structure

Now, let’s update your Shared ➔ build.gradle file to make the module work as multiplatform.

apply plugin: 'kotlin-multiplatform'

kotlin{
    targets {
        //Xcode sets SDK_NAME environment variable - based on whether the
        //target device is a simulator or a real device, the preset should vary
        final def iOSTarget  = System.getenv('SDK_NAME')?.startsWith("iphoneos") \
                                ? presets.iosArm64 : presets.iosX64

        //outputKinds - FRAMEWORK would mean that the shared code would be exported as a FRAMEWORK
        // EXECUTABLE - produces a standalone executable that can be used to run as an app
        fromPreset(iOSTarget, 'ios') {
            binaries {
                framework('Shared')
            }
        }
        //create a target for Android from presets.jvm
        fromPreset(presets.jvm, 'android')

    }
    //we have 3 different sourceSets for common, android and iOS.
    //each sourceSet can have their own set of dependencies and configurations
    sourceSets {
        commonMain.dependencies {
            api 'org.jetbrains.kotlin:kotlin-stdlib-common'
        }

        androidMain.dependencies {
            api 'org.jetbrains.kotlin:kotlin-stdlib'
        }
        
        iosMain {
        }
    }
}
configurations {
    compileClasspath
}

// This task attaches native framework built from ios module to Xcode project
// Don't run this task directly,
// Xcode runs this task itself during its build process when we configure it.
// make sure all Gradle infrastructure exists (gradle.wrapper, gradlew)
//and gradlew is in executable mode (chmod +x gradlew)
task packForXCode(type: Sync) {
    final File frameworkDir = new File(buildDir, "xcode-frameworks")
    final String mode = project.findProperty("XCODE_CONFIGURATION")?.toUpperCase() ?: 'DEBUG'
    final def framework = kotlin.targets.ios.binaries.getFramework("Shared", mode)
    inputs.property "mode", mode
    dependsOn framework.linkTask
    from { framework.outputFile.parentFile }
    into frameworkDir
    doLast {
        new File(frameworkDir, 'gradlew').with {
            text = "#!/bin/bash\nexport 'JAVA_HOME=${System.getProperty("java.home")}'\ncd '${rootProject.rootDir}'\n./gradlew \$@\n"
            setExecutable(true)
        }
    }
}

tasks.build.dependsOn packForXCode

We are doing three things in the above codebase:

  1. Listing out the target for the shared code. For Android, JVM target. For iOS, target depends on the device type, i.e. simulator or a real device.
  2. We have defined iOS, Android and common source sets, which will allow different configuration for each source set.
  3. We have created a task for Xcode to generate framework and add it to our iOS project.

Writing Shared code

For this tutorial we will keep it very simple and create a module that will return current Date in string format. Since JVM Date() and iOS NSDate() are different, so we have to update our common code something like this:

In Shared module, create a file commonMain➔kotlin➔Time.kt. Update the code as:

package com.mobiraft.shared

// We will mark the function as expect when we need to add platform specific code.
expect fun getCurrentDate(): String

// This the common function that will be called by Android and iOS app.
fun getDate(): String {
    return "Current Date is ${getCurrentDate()}"

Now, let’s add platform specific code in our androidMain directory for actual implementation of getCurrentDate() function for android.

Create a file in androidMain➔kotlin➔AndroidTime.kt and add the below code:

import java.util.*

actual fun getCurrentDate(): String  {
    return Date().toString()
}

Finally, let’s add the iOS platform specific code in iosMain➔kotlin➔IosTime.kt

import platform.Foundation.NSDate

actual fun getCurrentDate(): String {
    return NSDate().toString()
}

On iOS, date is retrieve using NSDate(). To access native iOS libraries or API in Kotlin/Native we have to import platform.* package.

With our iOS code also in place, the implementation is complete. Now let’s setup our iOS and Android app to run our Shared module.

Running Android App

On Android running the Shared module is similar to using any other package.

Goto you app’s build.gradle file and add this line:

implementation project(':Shared')

To use your module function just import it in your file and use it like any other Android module.

package com.mobiraft.kotlinmpp

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.TextView
import getDate //fun from our Shared Module.

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        findViewById<TextView>(R.id.label).text = getDate()
    }
}

As you can see above that we are directly just calling our shared function like any other function.

In your Shared module directory structure, instead of adding kotlin folder you can add a package name such as com.xyz.abc. This will give better readability to your shared module function when you import them.

Running iOS App

Let’s create a new iOS app which will use our Shared module.

Create new iOS project

Launch your Xcode IDE (available on Mac) and select create a new Xcode project.

Select Single View App from the template.

xcode getting started
Choose template for iOS App

Give your project a name and save it.

Now our iOS project setup is complete, let’s link it with our shared code.

Add the Shared Module

In our Shared Module build.gradle file we have added a task packForXCode

You can directly run the task from android studio to generate framework, it is usually created at Shared/build/Shared<Debug/Release>Framework/Shared.framework. This folder contains multiple files along with your framework. To make it easy to find the task, it also pushes out the framework file in build/xcode-frameworks.

Let’s add this framework to our Xcode project.

  1. Click on the project under project navigator window.
  2. Under general tab, scroll down to see Frameworks, Libraries and Embedded content section.
Use kotlin muliplatform framework in xcode

Click on the➕ icon in Frameworks, Libraries and Embedded Content section and from the bottom select Add Other➔Add Files.

Navigate to the Shared module directory and select build/xcode-frameworks/Shared.framework

Once the framework is added, next step is to configure Xcode with the search location of the framework.

Select the Build Setting tab, search for Framework Search Paths, add the complete path to your framework from Shared Module directory.

To use your framework, open up the ViewController.swift file and import the Shared Module at the top of the file.

Update your file with the code below:

import UIKit
import Shared //Our Shared Module

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        let label = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 25))
        label.center = CGPoint(x: 180, y: 280)
        label.textAlignment = .center
        label.font = label.font.withSize(18)

        //Calling our module function
        label.text = IosTimeKt.getCurrentDate()
        view.addSubview(label)
    }


}

Here is the output of both the platform:

Kotlin multiplatform output in iOS
iOS
Kotlin Multiplatform output on android
Android Device

Hurray!! Your first Kotlin Multiplatform project is ready!

This will help you get started with Kotlin Multiplatform. This is just a scratch on the surface. From Sharing a Web service layer, business logic, data models and Database, There’s a lot we can achieve with Kotlin/Native.

Stay tuned for the upcoming tutorials where we will cover how to share Data model and Webservice layer in Kotlin Multiplatform.


Checkout another quick article on How To Add Splash Screen in SwiftUI.

If you want to learn how to use SwiftUI in your existing app, then you can read it here!

or If you want to learn how to use and convert your UIViewControllers in SwiftUI, then you can read it here!


Like it? Share with your friends!

One Comment

Your email address will not be published. Required fields are marked *