· 15 min read Posted by Kevin Galligan, Russell Wolf

KMMBridge Quick Start

For native mobile teams looking to publish Xcode Frameworks and Android Libraries from a shared Kotlin repo, our template project can have you up and running in minutes.

Quick Start

Much of this content will be a duplicate of the original Quick Start Guide, with edits for the new version and template application. If you’ve been through the original, you can probably skim a bit, but make sure you set the Gradle group id and Xcode ~/.netrc auth.

Orientation

In summary, you need to be using GitHub and have the rights to create and administer repos for your org. For more detail, open “GitHub Prerequisites”

GitHub Prerequisites

GitHub Required

This template is configured as a standalone Kotlin repo, hosted on GitHub, relying on GitHub Actions and GitHub Packages. It can be a public or private repo.

If your team is using GitHub, unless you have a strange config or regularly run out of CI or hosting resources, you should be able to use this project immediately.

However, if:

  1. You don’t use GitHub, or need a different CI
  2. You’d rather publish an iOS Kotlin module from an existing Android repo

you will need to do some more work to integrate KMMBridge.

In any case, we still strongly suggest you finish this Quick Start. You will learn how publishing works, and make sure your configuration is functional before attempting other custom integrations.

Prep Work

  • Make sure you have admin access to your Github org.
    • At a minimum, you’ll need to be able to create another repo, or have somebody else create one from our template. If you want to publish to CocoaPods (details in the next post), you’ll need to create a publishing repo and have rights to grant access to it, so there will be a few admin-level steps.
  • Make sure you’re able to use GitHub Actions for builds and Github Packages for artifact hosting.
    • Generally speaking, all account types, free or not, should be able to use these features. Worst case, you can test out the Quick Start in a personal account before moving it to your “work” account.
    • Once you get this up and running, you can move builds to other CI and hosting platforms. However, as mentioned, we suggest you finish this Quick Start.

Required Setup

You will need to be able to build Android and iOS apps, with KMP code, on your local machine. Technically you could run the Quick Start template on CI without that, but you’ll have no way of testing anything.

If you don’t regularly code KMP, we would highly suggest getting a sample project running first. That should help isolate any potential trouble you run into.

Swift Package Manager vs CocoaPods

The KMMBridge Quick Start will be in two parts: basic setup with SPM (this post), and additional CocoaPods setup (the next post). You can publish to either or both with the same project, and the initial setup for both is very similar, but CocoaPods has several extra steps. If you want to publish to CocoaPods, make sure to do both.

Project Structure

KMMBridge will work in any repo configuration, but there are some common patterns.

  1. Android project links directly to shared Kotlin, but publishes an external iOS binary.
  2. A separate repo from which both Android and iOS artifacts are versioned and published.
Our original Quick Start, and the original design of KMMBridge, was focused on the former. That was based on our client experience. After a lot of community feedback, it became clear that both options need to be simple to configure.

The Quick Start is configured as a separate KMP code repo. #2 in the list above. You publish versioned binaries for both Android and iOS consumers.

There are 2 local test projects that allow you to build and test your Kotlin code directly, both Android and iOS. You can then “publish” both builds, with automatic “patch” versioning (ex. “1.3.534”), to the GitHub Packages repo attached to the Quick Start project. Assuming the rest of your team has access to your repo, you can immediately include those builds in any app (although you’ll probably want to write some code specific to your app. The Quick Start code is unlikely to be useful 🐶).

KMMBridge also supports publishing an iOS Framework from a module in your Android codebase. #1 in the list above.

It can also publish a binary in a monorepo, as even teams that share single repo may not want to be always on the latest shared code.

Go!

1) Use Our Template Repo to Create Your Kotlin Repo

Open the starter template project:

touchlab/KMMBridgeSKIETemplate

Click “Use Template”, give your repo a name, and create it. It can be public or private.

2) Edit GROUP

Very Important! You’ll need to edit the group string for your repo before you publish. Open gradle.properties and edit GROUP. The value for GROUP needs to be a valid maven coordinate string. Generally speaking, it should be reverse domain (“com.whatever”) and a name for the project, all lower case.

GitHub Packages can have trouble publishing when different repos have the same coordinates, even though their different repos. To avoid this, and the confusing errors it generates, make sure to change the GROUP value before publishing

You can make this edit directly in the GitHub web interface and commit it immediately. If you clone your repo and edit locally, obviously you’ll need to commit and push before the next step…

3) Publish A Build

After the repo has been created and GROUP has been specified:

  1. Open the repo page on GitHub.
  2. Go to “Actions” and run one of the available CI workflows.

Both actions are similar, but publish different artifacts.

  • All Publish Will publish iOS and Android binaries.
  • iOS Publish will build and publish just the iOS SDK. No Android binaries.

Select All Publish and run the workflow.

Run Workflow

Which build you should generally use depends on how you’ll be editing and publishing your shared Kotlin. Referring back to Project Structure, if your Kotlin code is in your Android repo and you’re only publishing an iOS build (option 1), use iOS Publish. If you have a separate repo for your Kotlin code and want to publish Android and iOS packages, use All Publish. For the rest of this post, we’ll assume you ran All Publish.

Check In #1 (~15 minutes)

Assuming no major surprises, you should have your first build processing. The build will take a fair bit of time. 10-15 minutes.

As always, if something didn’t work out, reach out

Running Test Apps

While your CI is running, let’s discuss the test applications in the template repo. When you publish binaries, other projects will get them from GitHub Packages. However, the test apps build and run the Kotlin code locally. The concept is that you can build and test locally, then commit and publish binary versions.

This is a common setup for larger apps with feature modules and teams. You have a smaller, local test app that lets you do faster, focused development on a particular feature, then the bigger “final app” pulls in the published module.

Android

The Android app is already set up to run. If you are in Android Studio or Intellij, the configuration should have been detected, and you can probably just run it from the actions bar without any other configuration.

Run Android

iOS

Open testapps/ios/ios.xcodeproj with Xcode. The test project uses Direct Integration to build the Kotlin Framework. You should be able to pick a simulator (iPhone only!) and run the project. Kotlin builds for iOS can take some time, but just let it run.

Select Simulator

What are the local development options for iOS with KMP?

For most app projects, you are using Xcode tools to compile and assemble an app binary and run it in an iOS simulator. The Kotlin project will produce an Xcode Framework that the iOS/Xcode project and include and call from Swift or Objective-C.

By “local development”, we the process by which Kotlin code gets built, locally on your dev machine, and then included in the iOS app project.

There are 2 main ways provided by JetBrains: CocoaPods local dev and Direct Integration.

CocoaPods local dev mode essentially uses CocoaPods to configure Xcode to build the Kotlin code and add it to the app. While many team use CocoaPods, many avoid CocoaPods entirely, and the idea of using CocoaPods to build Kotlin is not well received. To some degree, this is a non-issue, because using local CocoaPods for building Kotlin doesn’t require you to do anything with CocoaPods for the rest of the app. However, CocoaPods does require installed tooling, and the config can get broken if different devs have different versions. There are also some interesting quirks with the Kotlin CocoaPods support.

Direct Integration is fairly simple. You add a build phase to Xcode that runs the Gradle task embedAndSignAppleFrameworkForXcode. Xcode provides various environment variables about what target you are trying to build, and embedAndSignAppleFrameworkForXcode can interpret them to build the correct output. You also need to set up some search paths and linker flags.

CocoaPods was our generally preferred method until recently. We still use it, but for simple, direct setups, Direct Integration can be a better approach.

What we have not worked on yet with Direct Integration is a way to “toggle” local development on and off, to test a larger app against the shared Kotlin code directly. We do have docs explaining that for CocoaPods and SPM.

Besides the “official” local dev options, KMMBridge implements a very simple local dev flow using SPM. We currently do not hook into Xcode automatically for the build. You need to run it on the command line. However, time permitting, we will attempt to improve that integration in the future.

Success! (?)

If the build ran successfully, here is what you should expect to see.

The GitHub Actions Panel

The name of the workflow you ran should be in the list, with a green checkmark next to it. The full build is usually 15-20 minutes.

GitHub Actions Panel

Git Tags

Versions for SPM are marked by tags in the repo. Our build adds a tag for each build, based on the LIBRARY_VERSION property in gradle.properties. In the template, the value is set to 0.1. Our automatic versioning starts at 0 and counts up. the first version should be 0.1.0.

GitHub Tags

GitHub Packages

Our template is configured to store published builds in GitHub Packages. We built All Publish, which builds Android and iOS. The iOS artifact is just a zip file with a special Maven artifact name (kmmbridge).

We only use maven as a place to store a binary file. For that artifact, none of the other files really matter.

The Android publication adds several other artifacts.

GitHub Packages

Public Example

For this post, I created a repo from the template. You can see the build output there: touchlab-lab/KMMBridgeTest

What if it fails?

Typically, a deployment would only fail during the build phase, before publishing, or if publishing access is not configured correctly. Publishing to CocoaPods is much more likely to fail, at least early on while sorting out configuration (we’ll walk through publishing with CocoaPods in the next post).

Our publishing interface actually pushes artifacts before everything is finalized. If the build fails after that push, you will have artifacts in the published repo, but no version recorded. If that happened, and you attempted to publish again with the same version, the publish would fail (with file hosts that don’t let you overwrite, such as GitHub Packages).

To avoid this, we apply marker tags with our GitHub Actions Workflow. These are cleaned up on a successful publication. The summary is, if you have a failed build, you may see a marker tag in your GitHub tags, and after a successful build, the release tags will have a gap.

  • 0.1.0
  • 0.1.1
  • 0.1.4
  • 0.1.5
  • Etc

That would mean that something went wrong when publishing 0.1.2, and again for 0.1.3, but was resolved for 0.1.4. The build would need to succeed before publishing, so any error like this was a publication-related error. Once correctly configured, this should rarely, if ever, happen, but it is possible.

Read more about the publishing workflow and versioning mechanism here.

Configure IDE Clients

In this Quick Start we are using GitHub Packages. To access your binaries, both Android and iOS clients will need to point to your repos to access them. Also, while your repo can be public or private, GitHub Packages requires a user to authenticate to access the packages.

You can host binaries for public, unauthenticated access, but you’ll need to find another place to put them. KMMBridge can be configured to push binaries pretty much anywhere, but you may need to write a custom ArtifactManager.

For either client, Gradle or Xcode, you need to set up GitHub authentication to access the GitHub Packages binary. Xcode has an additional step for SPM, but only when using private repos. That is confusing. It would be great if GitHub Packages just didn’t do that (hint: write to GitHub Support).

For Every User

You need a GitHub username, and a Personal Access Token with at least repo and read:packages permissions.

Creating a Personal Access Token

To create a GitHub PAT, open GitHub, then find your user settings. Open “Developer Settings” at the bottom of the left menu.

Select Personal Access Tokens and select “classic”.

pat1

Create a new token, again “classic”.

pat2

Give it a name, select expiration, then make sure to select repo and read:packages permissions.

pat3

Make sure to copy that PAT string. It should look something like ghp_A_WHOLE_BUNCH_OF_CHARS_AND_NUMBERS

Gradle (Android Studio/Intellij)

For Gradle, you need to add the maven repository where your package is stored, along with the authentication information.

In one of your Gradle build files you’ll need to add the repo.

repositories {
    maven {
        name = "MyRepoName"
        url = uri("https://maven.pkg.github.com/[your org]/[your repo]")
        credentials {
            username = "[username]"
            password = "[PAT string]"
        }
    }
}

Generally speaking, you don’t want to put you username, and certainly not your PAT, directly in the Gradle code. Our recommended setup is to use a Gradle property.

credentials {
    username = project.property("GITHUB_PACKAGES_USERNAME") as String
    password = project.property("GITHUB_PACKAGES_PASSWORD") as String
}

On your local machine, you can add these values to your global Gradle properties ~/.gradle/gradle.properties. For CI, you can add them as secrets and pass them into Gradle.

With that configured, assuming your build published successfully, you should be able to use your library from an Android/Gradle project.

Xcode

When using GitHub Packages, or any repo that requires user authentication, you will need to add auth to your system config. This is true whether you are using SPM or CocoaPods to configure your dependencies.

You will use the same username and PAT that you used for Gradle. On your local machine, create or open the file ~/.netrc. This will be in your home directory. It may already exist, but if not, create it.

Add the following:

machine maven.pkg.github.com
  login [your username]
  password [your PAT]

Put your username in the login field, and the PAT in the password field.

Alternatively, you can use the Mac’s keychain to manage access. See this blog post for more detail. We have not tested this, but it should work.

Check In #2 (~30 minutes-ish)

Check the GitHub Actions panel to see if your build is done. If it is still processing, please wait till it is complete before moving on to the next steps.

Add the Framework to Xcode

Assuming you’ve configured your ./netrc file above, you can now access your build from Xcode.

If you have a private repo, you will also need to give Xcode access to your repo. It does not use the ./netrc file for this. It is a strange quirk, but yes, you need to configure GitHub auth twice.

Swift Package Manager (SPM)

To add your Framework to Xcode with SPM, do the following:

  1. Get the GitHub URL from your new repo (I generally copy the clone https URL)
  2. In Xcode, go to File > Add Packages to open the SPM tool
  3. Paste your GitHub URL in the “Search or Enter Package URL”
  4. Select your repo and for “Dependency Rule”, select “Up to Next Major Version” (or next minor version, either will pick up patch releases). Xcode should auto-detect the version.

Important!!! The sample Kotlin code needs to link to Sqlite on iOS, and the SPM support doesn’t have a way to specify those dependencies yet. You’ll need to open “Build Settings”, find “other linker flags”, and add -lsqlite3.

That’s it! You should be able to import your Kotlin code now. Watch the video to see how to use the SPM library:

Build and Run

In your iOS project, you should be able to call a test Kotlin function we’ve added to the iOS code. For example, in the default Xcode SwiftUI project template, try the following. Add import allshared, then call StartSDKKt.sayHello().

import SwiftUI
import allshared // <- import your new module

struct ContentView: View {
    var body: some View {
        Text(StartSDKKt.sayHello()) // <- Say Hello!
            .padding()
    }
}
Xcode may give you an error about import allshared before you build. Xcode indexing is not always in sync.

Final Check In (less than an hour, hopefully 😎)

Congratulations, you’ve now built, published, and consumed Kotlin Multiplatform dependencies. That pretty much covers what KMMBridge does.

If your team uses GitHub, you can go right ahead and hack away at the code in the template repo. If you are using something other than GitHub, you’ll likely need to do more configuration. Please see the KMMBridge docs. You can also reach out if you have specific questions.

Touchlab works with many teams who are investing in KMP to accelerate their adoption and ensure success. Check out our services for more information, and take a look at our premium support options with Touchlab Pro.

CocoaPods

If you want to publish to CocoaPods, contine to the CocoaPods setup in part 2.

Next Steps

Here are a few things to check on now that you’re done.

  • Check out the KMMBridge Doc on GitHub for some more info.
  • Join #touchlab-tools on the KotlinLang slack to discuss with other developers
  • Sign up for our newsletter to keep up with our other projects for multiplatform teams