How to start use Kotlin Multiplatform for mobile development

1. Set up Kotlin Multiplatform Project

  • Android Studio 3.4.0+ (do not use 3.5.1 version, cause there is a bug is breaking MPP project);
  • Xcode 10.3+;
  • Xcode Command Line Tools (xcode-select --install);
  • CocoaPods (sudo gem install cocoapods).
├── android-app
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src
│ ├── androidTest
│ │ └── java
│ │ └── com
│ │ └── icerockdev
│ │ └── android_app
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ │ └── com
│ │ │ └── icerockdev
│ │ │ └── android_app
│ │ │ └── MainActivity.kt
│ │ └── res
│ │ ├── drawable
│ │ │ └── ic_launcher_background.xml
│ │ ├── drawable-v24
│ │ │ └── ic_launcher_foreground.xml
│ │ ├── layout
│ │ │ └── activity_main.xml
│ │ ├── mipmap-anydpi-v26
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── icerockdev
│ └── android_app
│ └── ExampleUnitTest.kt
├── build.gradle
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── ios-app
│ ├── ios-app
│ │ ├── AppDelegate.swift
│ │ ├── Assets.xcassets
│ │ │ ├── AppIcon.appiconset
│ │ │ │ └── Contents.json
│ │ │ └── Contents.json
│ │ ├── Base.lproj
│ │ │ ├── LaunchScreen.storyboard
│ │ │ └── Main.storyboard
│ │ ├── Info.plist
│ │ └── ViewController.swift
│ └── ios-app.xcodeproj
│ ├── project.pbxproj
│ └── project.xcworkspace
│ └── contents.xcworkspacedata
└── settings.gradle

2. Create a shared library module

├── android-app
├── ios-app
└── mpp-library
apply plugin: 'com.android.library'
apply plugin: 'org.jetbrains.kotlin.multiplatform'
include ':mpp-library'
android {
compileSdkVersion 28
defaultConfig {
minSdkVersion 21
targetSdkVersion 28
}
}
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.icerockdev.library" />

Setup mobile targets

kotlin {
targets {
android()
iosArm64()
iosX64()
}
}
  • mpp-library/src/commonMain/kotlin/
  • mpp-library/src/androidMain/kotlin/
  • mpp-library/src/iosMain/kotlin/

Configure Android target

android {
//...
sourceSets {
main {
setRoot('src/androidMain')
}
release {
setRoot('src/androidMainRelease')
}
debug {
setRoot('src/androidMainDebug')
}
test {
setRoot('src/androidUnitTest')
}
testRelease {
setRoot('src/androidUnitTestRelease')
}
testDebug {
setRoot('src/androidUnitTestDebug')
}
}
}

3. Write common code

object HelloWorld {
fun print() {
println("hello common world")
}
}
kotlin {
// ...
sourceSets {
commonMain {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}
}
}
}

4. Android app realization

dependencies {
// ...
implementation project(":mpp-library")
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// ...
HelloWorld.print()
}
}
android {
// ...
minSdkVersion 21
// ...
}

Add Android-specific code

import android.util.Logobject AndroidHelloWorld {
fun print() {
Log.v("MPP", "hello android world")
}
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// ...
AndroidHelloWorld.print()
}
}

5. iOS app realization

cd mpp-library/src
ln -s iosMain iosArm64Main
ln -s iosMain iosX64Main
import platform.Foundation.NSLogobject IosHelloWorld {
fun print() {
NSLog("hello ios world")
}
}
kotlin {
targets {
// ...
def configure = {
binaries {
framework("MultiPlatformLibrary")
}
}
iosArm64("iosArm64", configure)
iosX64("iosX64", configure)
}
}

Integrate framework into iOS app

import UIKit
import MultiPlatformLibrary
class ViewController: UIViewController {
override func viewDidLoad() {
// ...
HelloWorld().print()
IosHelloWorld().print()
}
}

6. Connect shared library via CocoaPods

Setup framework compilation into a single directory

import org.jetbrains.kotlin.gradle.plugin.mpp.Framework
import org.jetbrains.kotlin.gradle.tasks.KotlinNativeLink
tasks.toList().forEach { task ->
if(!(task instanceof KotlinNativeLink)) return
def framework = task.binary
if(!(framework instanceof Framework)) return
def linkTask = framework.linkTask
def syncTaskName = linkTask.name.replaceFirst("link", "sync")
def syncFramework = tasks.create(syncTaskName, Sync.class) {
group = "cocoapods"
from(framework.outputDirectory)
into(file("build/cocoapods/framework"))
}
syncFramework.dependsOn(linkTask)
}

Setup local CocoaPod containing Framework

Pod::Spec.new do |spec|
spec.name = 'MultiPlatformLibrary'
spec.version = '0.1.0'
spec.homepage = 'Link to a Kotlin/Native module homepage'
spec.source = { :git => "Not Published", :tag => "Cocoapods/#{spec.name}/#{spec.version}" }
spec.authors = 'IceRock Development'
spec.license = ''
spec.summary = 'Shared code between iOS and Android'
spec.vendored_frameworks = "build/cocoapods/framework/#{spec.name}.framework"
spec.libraries = "c++"
spec.module_name = "#{spec.name}_umbrella"
spec.pod_target_xcconfig = {
'MPP_LIBRARY_NAME' => 'MultiPlatformLibrary',
'GRADLE_TASK[sdk=iphonesimulator*][config=*ebug]' => 'syncMultiPlatformLibraryDebugFrameworkIosX64',
'GRADLE_TASK[sdk=iphonesimulator*][config=*elease]' => 'syncMultiPlatformLibraryReleaseFrameworkIosX64',
'GRADLE_TASK[sdk=iphoneos*][config=*ebug]' => 'syncMultiPlatformLibraryDebugFrameworkIosArm64',
'GRADLE_TASK[sdk=iphoneos*][config=*elease]' => 'syncMultiPlatformLibraryReleaseFrameworkIosArm64'
}
spec.script_phases = [
{
:name => 'Compile Kotlin/Native',
:execution_position => :before_compile,
:shell_path => '/bin/sh',
:script => <<-SCRIPT
MPP_PROJECT_ROOT="$SRCROOT/../../mpp-library"
"$MPP_PROJECT_ROOT/../gradlew" -p "$MPP_PROJECT_ROOT" "$GRADLE_TASK"
SCRIPT
}
]
end

Connect our local CocoaPod to the project

# ignore all warnings from all pods
inhibit_all_warnings!
use_frameworks!
platform :ios, '11.0'
# workaround for https://github.com/CocoaPods/CocoaPods/issues/8073
install! 'cocoapods', :disable_input_output_paths => true
target 'ios-app' do
# MultiPlatformLibrary
pod 'MultiPlatformLibrary', :path => '../mpp-library'
end

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store