Multiplatform Programming Using Kotlin Native — a mobile developer’s quest! — Part 2

Arjun Kalidas
Dev Machina
7 min readJun 21, 2019

--

Now, that we have seen what Kotlin Multiplatform is and how we can setup the foundation for a multiplatform project. Let’s look at how it is done in Android and iOS with a sample app.

Using SharedModule from Android

Let’s add the SharedModule as a dependency to the main project. We can use the kotlin-multiplatform plugin directly in an Android project, instead of Kotlin-android plugin.

So, let’s patch the app/build.gradle file and include the below line into the dependencies { .. } block and perform a Gradle sync.

implementation project(‘:SharedModule’)
build.gradle file of the “app” module

Now let’s head to the layout.xml file of the app project and make some changes.

  • I gave a name “main_text” for the default “Hello World” text that appears in the android template. It looks like below:
Android layout window

Next, let’s include the following line of code into the “MainActivity”class from the /app/src/main/java/<package>/MainActivity.ktfile, to the end of the onCreatemethod:

findViewById<TextView>(R.id.main_text).text = createApplicationMessage()

Use the intention from the IDE to include the missing import line:

import com.kbcorp.sharedmodule.createApplicationMessage
MainActivity file of “app” module

Now, we have a TextView that will show our intended message as per the platform and the logic is written in the respective platform kotlin files by the name “createApplicationMessage()”.

It will show “Hello from Android” on Android emulator or device and “Hello from iOS” on iOS simulator or device.

Use this button in the IDE to execute the code and launch an emulator.

It should look like below:

Android emulator launched with the app

Creating iOS Application

Open Xcode and Create a new Xcode project. In the dialog box that opens, choose the iOS target and select the Single View App.In the subsequent menus, just accept the defaults and give a related name for your own understanding. For example KotlinIOS or IOSMultiplatform etc. And select the language as Swift (you can use Objective-C too.). While saving the project, choose a location inside the created Android project.

Create a new folder named “native” and save the iOS project inside the same.

Just to make sure everything is fine so far, run the application to see if the app is launching as expected. A basic single view app launches and displays and empty screen. It can be on either iPhone simulator or device.

Set up Framework Dependency in Xcode

The SharedModule build generates iOS frameworks for use with the Xcode project. We have to create and package the common code and supporting iOS modules as a framework, so that we can link them in as a dependency for the iOS project.

All the frameworks can be seen in the SharedModule/build/bin folder. It creates a debug and release version for every framework target. The frameworks are in the following paths:

SharedModule/build/bin/iOS/main/debug/framework/SharedModule.framework
SharedModule/build/bin/iOS/main/release/framework/SharedModule.framework

The way Gradle detects how to create a framework and for a simulator or a real device is from the configuration we have defined in the SharedModule/build.gradle file. It will be for either iOS x86_64 or iOS arm64.

Patching the Gradle Build Script

We have to automate this process of picking the right framework from the bin folder after it is created in the previous step and deposited in the corresponding folder. For that, a script need to be added to the SharedModule/build.gradle file.

Also, we have to make Xcode compile the framework before the build. Hence we will add this additional Gradle task at the end of the SharedModule/build.gradle file.

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(“SharedModule”, mode)inputs.property “mode”, modedependsOn framework.linkTaskfrom { framework.outputFile.parentFile }into frameworkDirdoLast {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

The details of the script above are :

  1. First a gradle task is defined, and defining it as a sync type. A “gradle sync” is performed as part of this function
  2. A framework directory is being created here by name ‘xcode-frameworks’ in the build directory
  3. We are finding the property XCODE_CONFIGURATION in Xcode and setting the mode as ‘DEBUG’
  4. Then referring the portion in the file that creates an iOS binary by name ‘SharedModule’
  5. Passing the mode as a inputs property
  6. Connecting this task with the linkTask (linkTask links the SharedModule with the Xcode Embedded binaries and Linked Frameworks and Binaries)
  7. Copying the generated framework output parent file to the build directory
  8. Then we are creating a new file called gradlew (Gradle wrapper)
  9. We mention a series of steps to carry out in this gradlew file as part of “doLast” function
  10. Making this file as an executable
  11. At last, we are asking Gradle to build with this task, by name “pack for Xcode”.

Let’s go to Android Studio and execute the build target of the SharedModuleproject from the Gradle tool window. The task looks for environment variables set by the Xcode build and copies the right variant of the framework into the SharedModule/build/xcode-frameworks folder. We then include the framework from that folder into the build.

Setting up Xcode for the Project

We have to add the SharedModule framework to the Xcode project.

For that let’s click on the root node of the project navigator and select the target settings. Next, we click on the +in the Embedded Binaries section, click Add Other…button in the dialog to choose the framework from the disk. We can point to the following folder:

SharedModule/build/xcode-frameworks/SharedModule.framework

We can see something like below:

Xcode launched with the KotlinIOS project

Now, we have to let Xcode know where to look for the frameworks. We have to add a relative path in the Search Paths | Framework Search Paths section.

$(SRCROOT)/../../SharedModule/build/xcode-frameworks

Go to ‘Build Settings’ tab and select the Framework Search Paths into the search field to easily find the option. Xcode will show generate the actual path and display it over here, although internally it will refer to a relative path.

The last step is to make Xcode call the Gradle build to prepare the SharedModule framework before each run. Go to the “Build Phases” tab and click ‘+’ to add the New Run Script Phase and add the following code into it:

cd “$SRCROOT/../../SharedModule/build/xcode-frameworks”./gradlew :SharedModule:packForXCode -PXCODE_CONFIGURATION=${CONFIGURATION}

Note, here we use the $SRCROOT/../..as the path to the root of our Gradle project. It can depend on the way the Xcode project was created. Also, we use the generated SharedModule/build/xcode-frameworks/gradlew script, the packForXCodetask generates it. We assumed that the Gradle build is executed at least once, before opening the Xcode project on a fresh machine.

We should place the created Run Script Phase to the top just below the Target Dependencies module. This is to let Xcode run this script first and link the SharedModule framework, if it has some changes.

Now we are all set to code the iOS app and we can leverage the common code written using Kotlin from the commonMain here.

Initializing Kotlin Code from Swift

Our aim is to display a simple text message on the screen. We are using a simple UILabel with a text message to convey the meaning. We will write our ViewController.swift like below:

import UIKitimport SharedModuleclass ViewController: UIViewController {override func viewDidLoad() {super.viewDidLoad()let label = UILabel(frame: CGRect(x: 0, y: 0, width: 300, height: 21))label.center = CGPoint(x: 160, y: 285)label.textAlignment = .centerlabel.font = label.font.withSize(25)label.text = CommonKt.createApplicationMessage()view.addSubview(label)}}

Here as you can see we are calling the “SharedModule” by using an “import” statement. This is compiled by using Kotlin/Native from Kotlin code. And we are calling this function like

CommonKt.createApplicationMessage()

This link will be helpful in giving more insight into this Kotlin/Native to Swift (or Objective-C Interop)

Now, let’s start our iOS application without much ado.

Run the iOS Application

Click on the “Run” button in Xcode and we can see our application running and display the text like below:

An iPhone simulator launched with the app

Yay! we made the app work on both the platforms with very little configurations. Now, to be honest, I came across many issues while setting this up, so to make your work easier I have listed those errors and solutions to fix them in part 3 of this series. So, don’t forget to check it out!

--

--

Arjun Kalidas
Dev Machina

I write about common software engineering problems & solutions to them, app development, machine learning, AI and blockchain! Hit me up on Twitter/LinkedIn.