Home Android & Kotlin Books Kotlin Apprentice

26
Kotlin Multiplatform Written by Joe Howard

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

You can unlock the rest of this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

As of 2019, Kotlin is the preferred language to use for Android development. On iOS, the Swift language has replaced Objective-C as the de facto development language.

Swift and Kotlin have a multitude of similarities, including static typing, type safety, support for the functional and OOP paradigms, and safe-handling of null values. The languages are also syntactically very similar.

The advent of Kotlin/Native has opened up the possibility of integrating Kotlin code into your iOS projects. In fact, the Kotlin Multiplatform (KMP) approach is beginning to take off as a cross-platform toolset for iOS, Android, and beyond.

In this chapter, you’ll use Android Studio and Xcode to create a KMP project.

The KMP Approach

Typical apps on iOS and Android pull down data from the Internet, parse the data into objects within the app code, and cache some of the network data locally in a database.

For iOS, you might use a library like Alamofire for networking and something like JSONSerialization to parse the data received from the network into objects. You’d use Core Data to store the data locally in a database. You’d implement an architecture pattern like MVVM to structure your app code. You might use a library like RxSwift to make your code more declarative. And you’d have tests for the view models and other parts of the app. You’d show lists of data in a UITableView in a UIViewController.

On Android, you’d have analogs of all of that code. You might use Retrofit for networking, Gson or Moshi for parsing the JSON, Room for storing the data in a database, a pattern like MVP or MVVM for the architecture, and maybe use RxJava in the app. You’d repeat similar tests. And you’d show list data in a RecyclerView in an Activity or Fragment using an adapter.

That’s a lot of duplication even for a simple app. Imagine that there were numerous screens in your app, with more data, networking calls, and local caching of the remote data, as there would be in a full-featured app. The amount of code duplication would grow essentially linearly with the size of the app, as would the amount of time and effort to first produce and then maintain the two apps for the two platforms.

Other Cross-Platform Frameworks

Reducing this duplication in targeting both iOS and Android has long been a vision of many developers and organizations in the mobile world. Early attempts included web frameworks such as PhoneGap. Organizations like Microsoft have produced tools like Xamarin, which uses C# and .NET to target iOS and Android. React Native, a derivative of the React web framework from Facebook, has become a popular modern framework for mobile. Most recently, Google has released the cross-platform framework Flutter, which uses its own runtime to allow apps written in Dart to perform at native speeds on iOS and Android.

Sharing Code

With Kotlin Multiplatform, you reduce code duplication by putting code common to all front-end apps into one module or shared project. This includes business logic, and things like networking code, data parsing, data peristence, and more.

HelloKMP

You’re going to build a simple app named HelloKMP that shares Kotlin code between iOS and Android apps. You’ll start in Android Studio and first setup the Android app project and the project level build files for the entire KMP project.

Renaming the app folder

The name of the android app folder is ideally something like androidApp instead of just the default app, in order to distinguish that part of the project as being the Android app.

mv app androidApp
include ':androidApp'
rootProject.name='HelloKMP'
rm androidApp/app.iml

Shared project

You’re going to build up the shared project more or less by hand. That way, you’ll see all that goes into creating the shared project.

mkdir -p shared/src/androidMain/kotlin
mkdir -p shared/src/commonMain/kotlin
mkdir -p shared/src/iosMain/kotlin
touch shared/src/commonMain/kotlin/common.kt
touch shared/src/androidMain/kotlin/android.kt
touch shared/src/iosMain/kotlin/ios.kt
touch shared/build.gradle.kts

Shared code build file

The shared code will be turned into the a jar file for running with the Android app, and an iOS framework for running with the iOS app. The shared project build.gradle.kts file is a Kotlin file where you will specify how that is done.

include ':shared'
include ':androidApp'
rootProject.name='HelloKMP'
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
plugins {
  kotlin("multiplatform")
}
kotlin {

}
//select iOS target platform depending on the Xcode environment variables
val iOSTarget: (String, KotlinNativeTarget.() -> Unit) -> KotlinNativeTarget =
  if (System.getenv("SDK_NAME")?.startsWith("iphoneos") == true)
    ::iosArm64
  else
    ::iosX64
iOSTarget("ios") {
  binaries {
    framework {
      baseName = "shared"
    }
  }
}
jvm("android")
sourceSets["commonMain"].dependencies {
  implementation("org.jetbrains.kotlin:kotlin-stdlib-common")
}

sourceSets["androidMain"].dependencies {
  implementation("org.jetbrains.kotlin:kotlin-stdlib")
}

expect and actual

Compiling the entire shared project for all platforms is not the approach taken by Kotlin Multiplatform. Instead a certain amount of code is common to all platforms, but some amount of the shared code is unique to each platform. The expect/actual mechanism has been added to Kotlin to allow for this.

package com.raywenderlich

expect fun platformName(): String
class Greeting {
  fun greeting(): String = "Hello, ${platformName()}"
}
package com.raywenderlich

actual fun platformName(): String {
  return "Android"
}
package com.raywenderlich

import platform.UIKit.UIDevice

actual fun platformName(): String {
  return "${UIDevice.currentDevice.systemName()}"
}

Shared code from Android

Now it’s time to use the shared library from the Android app.

packagingOptions {
  exclude 'META-INF/*.kotlin_module'
}
dependencies {
  implementation fileTree(dir: 'libs', include: ['*.jar'])
  implementation project(':shared')
  ...
}
android:id="@+id/greeting"
greeting.text = Greeting().greeting()
import com.raywenderlich.Greeting
import kotlinx.android.synthetic.main.activity_main.*

The iOS app

Having used the shared project in an Android app, you now turn to using the shared code in an iOS app. But first you need to setup the iOS app project itself.

mkdir iosApp

Packing the iOS framework

Next, back in Android Studio, you need to add a task to the Gradle build file shared/build.gradle.kts for the shared project that will package the framework for Xcode.

val packForXcode by tasks.creating(Sync::class) {
  val targetDir = File(buildDir, "xcode-frameworks")

  /// selecting the right configuration for the iOS
  /// framework depending on the environment
  /// variables set by Xcode build
  val mode = System.getenv("CONFIGURATION") ?: "DEBUG"
  val framework = kotlin.targets
      .getByName<KotlinNativeTarget>("ios")
      .binaries.getFramework(mode)
  inputs.property("mode", mode)
  dependsOn(framework.linkTask)
}
val packForXcode by tasks.creating(Sync::class) {
  ...
  from({ framework.outputDirectory })
  into(targetDir)
}
val packForXcode by tasks.creating(Sync::class) {
  ...
  /// generate a helpful ./gradlew wrapper with embedded Java path
  doLast {
    val gradlew = File(targetDir, "gradlew")
    gradlew.writeText("#!/bin/bash\n"
      + "export 'JAVA_HOME=${System.getProperty("java.home")}'\n"
      + "cd '${rootProject.rootDir}'\n"
      + "./gradlew \$@\n")
    gradlew.setExecutable(true)
  }
}
tasks.getByName("build").dependsOn(packForXcode)
ls -l shared/build/xcode-frameworks

cd "$SRCROOT/../../shared/build/xcode-frameworks"
./gradlew :shared:build -PXCODE_CONFIGURATION=${CONFIGURATION}

Shared code from iOS

With the iOS app project in place and linked up with the shared project, next you’ll use the shared code in the iOS version of the HelloKMP app. The iOS app will consist primarily of user interface code, relying on the shared code to do most of the work.

import UIKit
import shared
class ViewController: UIViewController {
  @IBOutlet weak var greeting: UILabel!
  override func viewDidLoad() {
    super.viewDidLoad()
    greeting.text = Greeting().greeting()
  }
}

Challenge

You have a real albeit simple Kotlin Multiplatform app for iOS and Android that uses shared code between the two platforms.

Key points

  • Kotlin Multiplatform is a new and fast-growing approach to cross-platform app development.
  • KMP lets you share Kotlin code between iOS, Android, web, server, and more.
  • There are a number of advantages to KMP, including developer familiarity with Kotlin, native performance, native UI code, and the consolidation of your app business logic into a shared module across all platforms.
  • You use the expect and actual keywords to create a common interface within the shared code that relies on concrete implementations on different platforms as needed.

Where to go from here?

You’ve just scratched the surface of Kotlin Multiplatform development in this chapter. There are a growing number of resources out there on KMP, so be sure to seek them out to see how to build more realistic apps, including doing things like networking, parsing JSON, and storing data locally in your app.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:

© 2021 Razeware LLC

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.