Kotlin Multiplatform and Github Actions

One of the fun challenges about working in mobile development is keeping up with the rapidly evolving landscape. With a major iOS and Android update happening every year along with new build and development tools being released all the time, mobile developers are constantly making updates to their codebase to support the changing requirements, often resulting in quicker development cycles while reducing the amount of code necessary to accomplish a task. 

Beyond the official tools and frameworks used to build an application there exists numerous libraries for handling in-app data storage, networking, serialization, threading, or in the case of the Nimbus SDK, rendering ads; each with their own release cycles and dependencies that are included in a build. As application developers, we are told we should always update libraries to the latest version as they contain bug fixes, security improvements, and increased compatibility with more devices. As the maintainer of a library, one needs to additionally build and maintain the infrastructure for publishing that library.

If you landed on this blog by searching for “how to publish an Android library,” you might have already come across some other posts on the topic or code in one of the many sample repositories on Github; some recommending the use of the deprecated Bintray plugin or suggesting the use of a plugin written by the blogger, which may not be actively maintained. The recent Alpha release of Kotlin Multiplatform Mobile has greatly simplified the amount of setup necessary for publishing an Android library. Using the latest versions of Kotlin Multiplatform and Gradle and the work just completed on the Nimbus OpenRTB library, I’ll walk through:

  1. Setting up a Kotlin Multiplatform library publication.

  2. Automating deployments using Github Action to Github Packages.

  3. Gradle plugins and managing versions with gradle.properties.

  4. Integrating with your current workflow.


Nimbus OpenRTB contains the models used to make a request to Nimbus and is included with the Nimbus Android SDK. Kotlin Multiplatform already ships with a cross-platform networking library (ktor) and serialization (kotlinx.serialization) library; by migrating Nimbus OpenRTB, we now have a path forward to deploy clients to additional platforms or build desktop and command line tools to support our ad ops.


Why Kotlin Multiplatform?

Kotlin Multiplatform Mobile (KMM) differentiates itself from other cross-platform frameworks by emphasizing the business logic of an application being written in a shared module and by providing language abstractions for calling platform-specific code using the keywords `actual/expect`. By keeping the UI implementation completely native, developers have complete control over the look and feel of the application without sacrificing performance and by providing integration with each platform’s build tools, allowing for the use of native frameworks such as Swift UI and Jetpack compose. With Jetbrains handling the development of both Kotlin and Multiplatform, along with the first class support on Android from Google and adoption by tech companies such as Netflix and Square, now is a good time to try KMM to start sharing code between platforms or quickly get setup with Android library publishing.

For more reading on Kotlin Multiplatform Mobile and instructions on how to setup your first project, check out the homepage - https://kotlinlang.org/lp/mobile/.


From library module to library publication

When you first create your Kotlin Multiplatform Mobile Project, you’ll have directories for the iosApp, androidApp, and shared module. Setting up Android publishing can be accomplished by applying the `maven-publish` and adding the publishLibraryVariants directive in the kotlin android block.


shared/build.gradle.kts

plugins {
   id("com.android.library")
   kotlin("multiplatform")
   `maven-publish`
}

kotlin {
   android {
       publishLibraryVariants("release")
   }
   ios {
       //...
   }
   sourceSets {
       //...
   }
}

*Note: The android library plugin is applied before multiplatform and kotlin android extensions that is part of the initial MPP project has been removed*

The `maven-publish` plugin will add the publishing tasks to your project and allow you to test your publication by running the `publishToMavenLocal` task.

Calling `publishLibraryVariants(“release”)` instructs the multiplatform plugin to set up the android publication for only the release variant. This will automatically add the source files to the publication allowing the consumer of the library to see any code documentation directly from Android studio. You can also setup your library to publish all variants by instead calling `publishAllLibraryVariants()`

For more information on publishing with Kotlin Mulitplatform.


Deploying to Github Packages

Github Packages is a new offering from Github that allows developers to host their own packages using popular package management software and get started for free. With unlimited data transfer when used within Github Actions, Github Packages is a good option if you want to modularize a build across multiple Github repositories.

To setup Github Packages as a publishing target, add the following to the bottom of the shared build script:


shared/build.gradle.kts

getenv("GITHUB_REPOSITORY")?.let {
   publishing {
       repositories {
           maven {
               name = "github"
               url = uri("https://maven.pkg.github.com/$it")
               credentials(PasswordCredentials::class)
           }
       }
   }
}

Because we are going to be using Github Actions to publish our project, we can rely on the `GITHUB_REPOSITORY` variable being present during the build and can automatically generate the correct publishing url. When developing locally, this publishing target will not be configured, preventing a developer from accidentally publishing the library.

credentials(PasswordCredentials::class) will instruct Gradle to look for project properties that match the repository name. You can define the user and password in the project’s gradle.properties file but we’ll use Actions to pass through that data.


gradle.properties

githubUsername=
githubPassword=


For more information on Github Packages.


One Click Deployment with Github Actions

While many CI platforms work with Github, the ease of setup, tight integration with the Github development workflow, and a free monthly allocation of machine use on all Github repositories, public or private, makes Actions a very attractive option for setting up a deployment pipeline. By leveraging the publishing tasks setup by Gradle and information from the build environment, we can quickly add a workflow to check and publish the library by creating a new release from the Github Releases page. 

Because creating a new release on Github creates a new tag as well, we can use the GITHUB_REF environment variable from the build to set the version name of the library that is about to be published. Let’s set the version and group on allprojects from the root project.


build.gradle.kts

allprojects {
  group = "template.mpp"
  version = System.getenv("GITHUB_REF")?.split('/')?.last() ?: "development"
}


When building or publishing locally, the library version name will default to ‘development’ but when published as part of a release will have the same name as the tag of the release.

Now copy the following code into a new file .github/workflows/release.yml to setup the publishing workflow.


.github/workflows/release.yml

on:
 release:
   types: [created]

jobs:
 release:
   runs-on: macos-latest
   steps:
     - name: Checkout code
       uses: actions/checkout@v2

     - name: Setup Java
       uses: actions/setup-java@v1
       with:
           java-version: 11

     - name: Publish
       run: gradle check publish --no-configure-on-demand --no-daemon
       env:
           ORG_GRADLE_PROJECT_githubUsername: ${{ github.actor }}
           ORG_GRADLE_PROJECT_githubPassword: ${{ github.token }}

*Note: enabling configure on demand can cause hard to troubleshoot build issues*

This is a very standard workflow similar to the templates Github provides but with the addition of passing through the credential necessary to publish our library.

For more information on Github Actions.


Integrating into your current workflow

Now that you have your library publishing to Github you can consume it just like you would any other build.

Add the repository definition to your projects, we’ll name this ‘mpp’

allprojects {
   repositories {
       maven {
           url "https://maven.pkg.github.com/timehop/kotlin-mpp-template"
           name = "mpp"
           credentials(PasswordCredentials)
       }
   }
}


Now let’s add the credentials. This time we are going to use the gradle.properties file in the gradle home directory. Gradle will look for the credentials in this file if they have not been defined in the project properties which makes this a good place to put personal access tokens so they do not get accidentally checked in.

~/.gradle/gradle.properties

mppUsername=yourGithubUsername
mppPassword=yourGithubPersonalAccessToken



That’s It!

If you want to get started right now I have created a template repository in Github you can use to create a new project, add code, and start publishing immediately: https://github.com/timehop/kotlin-mpp-template


Bonus: Versioning as a configurable build properties

Gradle allows the developer to set project properties using the root project gradle.properties file. We can define the version definitions for our build tools so we only have to define it in a single place.

gradle.properties

## Android Gradle Plugin
agp=4.1.1

## Kotlin Gradle Plugin
kgp=1.4.20


Now let’s update the the pluginManagement block in the root settings.gradle.kts file to reference these properties

settings.gradle.kts

pluginManagement {
   repositories {
       gradlePluginPortal()
       google()
   }
   val agp: String by settings
   val kgp: String by settings
   resolutionStrategy {
       eachPlugin {
           when(requested.id.namespace) {
               "com.android" -> useModule("com.android.tools.build:gradle:$agp")
               "org.jetbrains.kotlin" -> useVersion(kgp)
           }
       }
   }
}


Notice how the agp and kgp values are set via the settings delegate. This will search in gradle.properties or properties set from the command line that match the name of the variable.

Testing updates to the build tools can now be done by simply updating the version number in the gradle.properties.

For more information on the Gradle Properties used in the build -https://docs.gradle.org/current/userguide/build_environment.html

twitter facebook facebook