Chapters

Hide chapters

Kotlin Multiplatform by Tutorials

Second Edition · Android 14, iOS 17, Desktop · Kotlin 1.9.10 · Android Studio Hedgehog

14. Creating Your KMP Library
Written by Carlos Mota

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

In the previous chapters, you’ve built the learn app for Android, iOS and desktop. All of these apps fetch the Kodeco RSS feed and show you the latest articles written about Android, iOS, Flutter, Server-side Swift, Game Tech, and Professional Growth. You can search for a specific topic or save an article locally to read it later. During the app’s development process, you’ve worked with:

  • Serialization
  • Networking
  • Databases
  • Concurrency

And, along this journey, you’ve also built additional tools that are useful and can be reused in other projects:

  • Logger
  • Parcelize support

In this chapter, you’re going to learn how you can create and publish a library so you can reuse it in the other apps that you develop in this book — and for the next one you’re going to build. :]

Migrating an Existing Feature to Multiplatform

Throughout this book, you’ve learned how to develop a project that had a library already shared across different platforms. However, you may want to migrate an existing app to Kotlin Multiplatform.

In this section, you’re going to see how a simple feature like opening a website link in a browser can easily be moved to KMP.

Learning How to Open a Link on Different Platforms

In learn, when you click on an article, a web page opens — whether it’s on Android, iOS or desktop. The behavior is similar on all three platforms, although the implementation is entirely different.

private fun openEntry(url: String) {
  val intent = Intent(Intent.ACTION_VIEW)
  intent.data = Uri.parse(url)
  startActivity(intent)
}
@Environment(\.openURL)
var openURL
openURL(URL(string: "\(item.link)")!)
fun openEntry(url: String) {
  try {
    val desktop = Desktop.getDesktop()
    desktop.browse(URI.create(url))
  } catch(e: Exception) {
    Logger.e(TAG, "Unable to open url. Reason: ${e.stackTrace}")
  }
}

Adding a New Module

The first thing to decide is if you want to move this logic to the existing shared module or create a new one. Since adding a new library also requires you to migrate the code, you’re going with this more complete solution.

plugins {
    alias(libs.plugins.jetbrains.kotlin.multiplatform)
    alias(libs.plugins.android.library)
}
include(":shared-action")
jvm("desktop")
getByName("desktopMain") {
  dependencies { }
}
jvmTarget = JavaVersion.VERSION_17.toString()
compileOptions {
  sourceCompatibility = JavaVersion.VERSION_17
  targetCompatibility = JavaVersion.VERSION_17
}
Variable 'commonMain' is never used
getByName("commonMain") {
  dependencies {
    //put your multiplatform dependencies here
  }
}
Fig. 14.1 — Project structure
Pov. 60.0 — Tvijujn smzewgaco

Fig. 14.2 — Android Studio project view
Yon. 30.0 — Ornzeaf Yhunue mlazuly jiev

Configuring an Android Library to Publish

To publish the Android libraries, add the following code to the the androidTarget definition in the kotlin section of the build.gradle.kts file from shared-action to:

androidTarget {
  publishLibraryVariants("release", "debug")
}

Configuring a Multiplatform Swift Package

You have different possibilities to generate a library. Since Apple has its own package manager — Swift Package Manager — and many libraries are now available through it, you’re going to use it in this chapter.

includeBuild("plugins/multiplatform-swiftpackage-m1_support")
id("com.chromaticnoise.multiplatform-swiftpackage-m1-support")
//1
multiplatformSwiftPackage {
  //2
  xcframeworkName("SharedAction")
  //3
  swiftToolsVersion("5.3")
  //4
  targetPlatforms {
    iOS { v("13") }
  }
  //5
  outputDirectory(File(projectDir, "sharedaction"))
}
val xcf = XCFramework("SharedAction")
listOf(
  iosX64(),
  iosArm64(),
  iosSimulatorArm64()
).forEach {
  it.binaries.framework {
    baseName = "SharedAction"
    xcf.add(this)
  }
}
./gradlew shared-action:createSwiftPackage
id("com.chromaticnoise.multiplatform-swiftpackage-m1-support")
multiplatformSwiftPackage {
  xcframeworkName("SharedKit")
  swiftToolsVersion("5.3")
  targetPlatforms {
    iOS { v("13") }
  }
}
./gradlew createSwiftPackage

Migrating the Code to Multiplatform

Now that you’ve got everything configured, it’s time to move the code from the app’s UI to Multiplatform. Since the shared-action module is going to deal with the user action of opening a link, in the Action.common.kt that you’ve created inside commonMain, add:

public expect object Action {

    public fun openLink(url: String)
}
public expect fun openLink(url: String)
PlatformActionKt.openLink(url: "\(item.link)")
public actual object Action {
  public actual fun openLink(url: String) {}
}
public actual fun openLink(url: String) {
  val intent = Intent(Intent.ACTION_VIEW)
  intent.data = Uri.parse(url)
  startActivity(intent)
}
public lateinit var activityContext: Context
activityContext.startActivity(intent)
import android.content.Context
import android.content.Intent
import android.net.Uri
public actual fun openLink(url: String) {
  try {
    val desktop = Desktop.getDesktop()
    desktop.browse(URI.create(url))
  } catch(e: Exception) {
    Logger.e(TAG, "Unable to open url. Reason: ${e.stackTrace}")
  }
}
println("Unable to open url. Reason: ${e.stackTrace}")
import java.awt.Desktop
import java.net.URI
Fig. 14.3 — Apple documentation for OpenURL function
Bul. 84.2 — Adlfi damorazluwooh bed EyizIQT rujgraup

import platform.UIKit.UIApplication
UIApplication.sharedApplication.openURL(url)
public actual fun openLink(url: String) {
  val application = UIApplication.sharedApplication
  val nsurl = NSURL(string = url)
  if (!application.canOpenURL(nsurl)) {
    println("Unable to open url: $url")
    return
  }

  application.openURL(nsurl)
}
import platform.Foundation.NSURL
import platform.UIKit.UIApplication
./gradlew assemble
./gradlew createSwiftPackage

Adding a New Library to the Project

Before publishing a library, it’s important to mention that you can include it in your apps in two different ways:

Add as a New Dependency

At the same level as shared, you include it on Android and desktop build.gradle.kts files and add it to the iOS app project.

implementation(project(":shared-action"))
Fig. 14.4 — XCode project view
Had. 32.5 — LSeqe jfoheky reun

Include Inside the Shared Module

The existing shared module can import the library and makes its features available to all apps that use it.

implementation(project(":shared-action"))

Updating Your Apps to Use Your New Library

With the new library available to all platforms, it’s time to replace the existing logic with calls to the openLink function from shared-action.

private fun openEntry(url: String) {
  activityContext = this
  openLink(url)
}
import com.kodeco.learn.action.Action.openLink
import com.kodeco.learn.action.activityContext
import android.net.Uri
onOpenEntry = { openLink(it) },
@Environment(\.openURL)
var openURL
Action().openLink(url: "\(item.link)")
import SharedAction
Fig. 14.5 — Android app: Open an article
Tis. 65.4 — Oprbuuc ict: Ageh ug ezgegru

Fig. 14.6 — Desktop app: Open an article
Dov. 05.6 — Lugjdeh adh: Afuw uz uvcilgi

Fig. 14.7 — iOS app: Open an article
Xoz. 54.2 — uEZ agb: Ojaf am iscaqbu

Publishing Your KMP Library

In all the projects you’ve developed throughout this book, both the shared module and the apps were under the same repository. This made it easier to dive into Kotlin Multiplatform and avoid configuring multiple repositories.

include(":androidApp")
include(":desktopApp")

include(":shared")
include(":shared-action")
include(":your-library")
project(":your-library").projectDir = file("../path/to/your-library")

Configuring a Library

You can access a library in any repository via its group, name and version number, with the following nomenclature:

version = "1.0"
group = "com.kodeco.shared"

How to Publish a Library Locally

At the end of the plugin section, add:

id("maven-publish")
./gradlew shared-action:publishToMavenLocal
./gradlew publishToMavenLocal
~/.m2/repository
include(":shared-action")
mavenLocal()
implementation(project(":shared-action"))
implementation("com.kodeco.shared:shared-action:1.0")
Fig. 14.8 — Android app: Open an article
Red. 72.7 — Omtsoek oqz: Ovim ab eszespa

Fig. 14.9 — Desktop app: Open an article
Fus. 87.4 — Mifswaj ugz: Elud up iyyehge

How to Publish a Library to the GitHub Packages Repository

There are a set of repositories that you can use to publish your libraries: JitPack, Maven Central and GitHub Packages. These are the most common. Or, you can always set up your own package repository.

Create Your Access Token

Log in to GitHub and go to your account Settings. You can see your Settings option by clicking your avatar in the top right corner of the website. Next, scroll down the page until you see Developer settings on the left and click there. You’ll be redirected to a new screen. From there, go to Personal access tokens, followed by Tokens (classic) and then Generate new token. From the drop-down that appears select Generate new token (classic).

Fig. 14.10 — GitHub token configuration
Pej. 92.70 — HiwHiy xayed gijpalixoceiy

Create a New Repository

To publish your libraries, you need a repository to push them. If you don’t have one created, go to the main GitHub page and click on New to create a new repo.

Publish Your Library

With the GitHub Package repository ready, return to Android Studio and open the gradle.properties file located in the root directory. Here, add your account username and the token that you copied earlier:

#Repository Credentials
mavenUsername=YOUR_USERNAME
mavenPassword=YOUR_TOKEN
publishing {
  repositories {
    maven {
      //1
      url = uri("https://maven.pkg.github.com/YOUR_USERNAME/YOUR_REPOSITORY")
      //2
      credentials(PasswordCredentials::class)
      authentication {
        create<BasicAuthentication>("basic")
      }
    }
  }
}
credentials {
  name = YOUR_USERNAME
  password = YOUR_TOKEN
}
include(":shared-action")
./gradlew shared-action:publish
Fig. 14.11 — GitHub published libraries
Duk. 56.94 — VojJot ridsifteq nikgusouf

maven {
  url = uri("https://maven.pkg.github.com/YOUR_USERNAME/YOUR_REPOSITORY")
  credentials(PasswordCredentials::class)
  authentication {
    create<BasicAuthentication>("basic")
  }
}
Fig. 14.12 — Android app: Browse through the latest articles
Rey. 36.84 — Usgloir akp: Nmospu vjbaixp jjo nufesv ossibkel

Fig. 14.13 — Desktop app: Browse through the latest articles
Fuy. 21.02 — Pidwvub idr: Gperqo vqwiuct kze goqefk ohrozwox

How to Publish Your Swift Package

With your GitHub repository already configured from the section above, you can use it to publish your Swift package.

./gradlew shared-action:createSwiftPackage
Fig. 14.14 — XCode add a Swift package from a custom URL
Qem. 34.46 — VFari izj a Mtuxf zomqetu vgut a tidsag ALD

Fig. 14.15 — XCode add a Swift package: Confirm
Haw. 33.96 — BPaqa usn e Dlity xucyemo: Cemwahz

Fig. 14.16 — iOS app: Browse through the latest articles
Bub. 35.74 — uIS usn: Bjefbu gjniimb mko zekovz ohqabqin

Handling Multiple Frameworks

Learn currently has three shared modules: shared, shared-dto, and shared-action. Once you are done with Challenge 1 mentioned below, you will have a total of 4 modules: shared, shared-dto, shared-action, and shared-logger.

val xcf = XCFramework("SharedAll")
listOf(
  iosX64(),
  iosArm64(),
  iosSimulatorArm64()
  ).forEach {
    it.binaries.framework {
      baseName = "SharedAll"
      xcf.add(this)

      export(project(":shared"))
      export(project(":shared-logger"))
    }
  }

Challenges

Here are some challenges for you to practice what you’ve learned in this chapter. If you get stuck at any point, look at the solutions in the materials for this chapter.

Challenge 1: Create a Logger

All the apps and the shared module use the Logger class defined on shared/PlatformLogger.kt. It’s a simple logger that calls on:

Challenge 2: Integrate the Logger Library

Throughout this book, you created three different apps:

Challenge 3: Use the Logger Library in the Shared-Action Module

At the beginning of this chapter, you successfully migrated the open links functions to Kotlin Multiplatform and created the shared-action module.

Key Points

  • If the features you want to migrate to KMP have any platform-specific code, you need to write this specific logic for all the platforms your library will target.
  • You can have multiple KMP libraries in your project, and even a KMP library can include another one.
  • To publish a library for Android and desktop, you can either publish it locally or to a remote package repository that supports both platforms (.jar and .aar). In this book, you’ve seen how to use GitHub Packages.
  • For iOS, you’re creating a Swift package to share your library. Apple requires that these frameworks need to be available through a Git repository, which can either be local or remote.

Where to Go From Here?

Congratulations! You’ve finished the last chapter of the book. Throughout this book, you learned how to create three apps targeting Android, iOS and desktop!

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.
© 2024 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now