KMMBridgeKMMBridge CocoaPods Quick Start

KMMBridge CocoaPods Quick Start

While KMMBridge does support CocoaPods, we generally recommend using SPM whenever possible with KMMBridge. With CocoaPods there’s more complexity, more room for failure and less support as the world is moving towards SPM. While CocoaPods is still supported, we rarely use it for our client projects at this point. Some docs may be out of date, so please reach out if you find anything that seems off.

Configuring KMMBridge from scratch is complex. That is because configuring XCFramework publications is complex. There are many points of access and resource configuration necessary.

If you are using GitHub, however, we have built a template project to make this process simple.

We have another “CocoaPods Quick Start” from the KMMBridge 0.5.x releases. If you are currently using KMMBridge from the 0.5.x era, please note several features have been simplified and/or removed. When upgrading, please carefully review your workflow script changes.

Publishing to CocoaPods requires a few steps, this is because CocoaPods keeps it’s dependency info in a separate git repo. That means you will need to:

  1. Create (or have access to) a GitHub repo dedicated to storing CocoaPods version information.
  2. Configure CI with an SSH key info to be able to push to that repo.
  3. Configure your clients to be able to access that repo (if not public).

CocoaPods publishes Podspec config info to a separate repo. This repo only holds config information. It does not host your binaries. The XCFramework binary files are pushed to the GitHub Packages of your KMP library repo.

Create a KMP Repo

Open up our GitHub template repo

From the GitHub page, click “Use this template”, and create a repo in your org.

“Use this template” screenshot Use this template button screenshot

This repo can be public or private. Private repos will allow you to control access through GitHub.

Change the GROUP value

Open gradle.properties and change the value of GROUP to something that doesn’t start with co.touchlab.

GROUP=com.yourorg.yourrepo
Why GROUP needs to be changed

This only matters for GitHub Packages, which has historically had issues with conflicting maven “coordinates”, even though they are in separate GitHub Package repos. It’s not super important to have your domain, but our workflow checks and fails the build if it starts with co.touchlab.

Set your build version

Your builds publish with a version. That is controlled by LIBRARY_VERSION in gradle.properties. Whenever you publish, be sure to update that value first.

The value currently in the template should work, but I’d suggest changing it to:

LIBRARY_VERSION=0.1.0

Make sure to commit and push that change.

Create a Podspec Repo

CocoaPods keeps it’s config info, called “Podspecs”, in a separate git repo. In our case, this means you’ll want to create a new empty repo on GitHub.

Create a repo in your org. We’ll call ours “Podspecs”. It can be public or private.

Important! Add at least one commit to this repo. The easiest option is to create a README. The CocoaPods process won’t push without an existing commit.

Next, you’ll need to enable access between these repos and our CI build workflow.

Pointing Your KMP Library to the Podspec Repo

In order to correctly have KMMBridge export the Podspecs for your library, you need to update the allshared/build.gradle.kts file to point to the new repo.

kmmbridge {
    // Must be the SSH url
    cocoapods("git@github.com:YOUR_ORG/YOUR_PODSPEC_REPO.git")
}

Make sure to use the SSH form of git remote URL rather than the https form.

Adding Deploy Keys

In order grant CI access to your repos you’ll need to add deploy keys. This can be done manually via the command line and GitHub, however for convenience we’ve added a gradle task called setupDeployKeys to help automate the process.

To use this gradle task you’ll have to install GitHub CLI and make sure you are authenticated with GitHub.

./gradlew setupDeployKeys \
  -PgithubDeploySourceRepo=YOUR_ORG/YOUR_KMP_REPO \
  -PgithubDeployTargetRepo=YOUR_ORG/YOUR_PODSPEC_REPO

The task setupDeployKeys creates an ssh public/private key pair on your local machine. Then it adds the public key to your target repos deploy keys, and the private key to the source repos action secrets (named KMMBRIDGE_SSH_KEY). It takes in two parameters: githubDeploySourceRepo and githubDeployTargetRepo. These parameters point to your KMP source repo and your Podspec deployment repo. They can either be passed in directly to the call or added to the gradle.properties file.

githubDeploySourceRepo=YOUR_ORG/YOUR_KMP_REPO
githubDeployTargetRepo=YOUR_ORG/YOUR_PODSPEC_REPO

If you get the error

Task 'setupDeployKeys' not found in root project 'YOUR_PROJECT' and its subprojects.

this means that these properties were not set up correctly.

If you want you can double-check that this task succeeded by checking your two repos:

  • In your KMP repo click on the “Settings” tab, then go to “Secrets and Variables” -> “Actions”. You should see KMMBRIDGE_SSH_KEY.
  • In your podspec repo click on the “Settings” tab, then go to “Deploy Keys”. You should see KMMBridge Key.

Publish your iOS Binaries

If you have a free GitHub account… Then you will need to set one additional setting in GitHub. By default paid accounts have actions set up to read/write, but for some reason free accounts default to read-only. You’ll need to update your repos settings.

Go to your KMP library repo and click the “Settings” tab. Then select actions -> general. Under workflow permissions select “Read and Write permissions”. Workflow permissions

Make sure you’ve committed your changes, then open your new KMP repo (not the Podspec repo) in GitHub, then open the “Actions” tab. Find “KMMBridge iOS Publish” in the list of Workflows.

“Actions” tab screenshot Show how to find the GitHub Action

Look for “Run workflow”. Leave the branch default (main), and click the green “Run workflow” button.

“Run workflow” screenshot Show how to find the Run workflow button

⏳ Wait (~15 minutes)…

The workflow will build 3 release binaries for iOS. This can take a fair bit of time. Usually around 15 minutes.

Faster CI Builds

KMP iOS builds in CI can be slow. There are various ways to improve build times, but publishing versioned binaries should be relatively rare, so the longer build time is usually not a huge issue.

To speed up this build, if all of your devs have the same mac architecture (M1-3 or Intel), then you can remove the “other” simulator target. For example, all Touchlab devs have Mac arm machines (M1-3). We could remove iosX64() from the list of architectures to build, and that would cut the build time by 1/3.

NOTE: Your CI runner also needs to be on the same architecture because CocoaPods attempts to build and run dependencies before publishing them. So, if your devs have ARM macs but your CI runs on Intel, you cannot remove iosX64().

For other CI build time tips see “Beware of Build Time Bloat”.

GitHub Access

To access your repo and binary packages, you’ll need to provide access to GitHub. That means creating a Personal Access Token (PAT). We’ll create one PAT to access everything from your local machine to test the builds, but every user will need to repeat this process for their access.

If your repo is public, you should be able to skip some of the scopes for the PAT, but I’m not sure how you’d access a public GitHub repo from the Xcode GUI. To make the process simpler for this tutorial, you should create a PAT with the full set of scopes.

GitHub requires auth for public repo binaries

GitHub requires users to set up authentication access to download binary files. This is obviously true for private repos, but it is also true for PUBLIC repos. It can be any valid GitHub user, but you still need to provide the auth info. This is a very common cause of confusion for KMMBridge users.

This limitation appears to only apply to GitHub Packages, not GitHub release binaries, but because of another unresolved issue with CocoaPods, we need to publish to GitHub Packages for this tutorial. If you publish to another location, this shouldn’t be an issue.

If you need to host CocoaPod or Android binary artifacts in a place that doesn’t require auth, you’ll need to use a different cloud back end. You can use AWS S3, but you’ll need to configure your S3 bucket to allow public URLs. If you want to use a different back end, implement an ArtifactManager instance.

In GitHub, create a Personal Access Token. Go to your user settings, “Developer settings”, “Personal access tokens”, “Tokens (classic)“. “Generate new token” and select the “classic” option.

For all users, add the following scopes:

  • repo
  • read:packages

GitHub PAT scopes screenshot Show GitHub personal access token scopes

Xcode requires these additional scopes:

  • admin:public_key
  • write:discussion
  • user

GitHub Xcode screenshot GitHub PAT screenshot

After the token is created, make sure to get and keep a copy of the token string. We’ll need to add it in a few places.

We use the “classic” PAT option because we know what scopes are needed, and most docs talk about those scopes. The “Fine-grained tokens” should also work, but the scope names are different and we’d need to do some testing.

Some Highlights (while we’re waiting)

While your build is running, let’s take a few minutes to highlight important parts of the source and configuration for your new repo.

If you want to skip ahead go to Check Your Build

Publishing

This quickstart guide publishes your libraries artifacts to GitHub Packages using maven. This is configured in the allshared/build.gradle.kts:

addGithubPackagesRepository()

kmmbridge {
    mavenPublishArtifacts()
    cocoapods(...)
}

This is because of an issue with CocoaPods using GitHub release artifacts. For now the project uses GitHub Packages, which is located at maven.pkg.github.com. You can see this url in the publish GitHub action and should be used when configuring your .netrc file, which is covered later in the guide.

Android Publication

You can also publish Android AAR dependencies from the KMP repo.

Assuming you ran the iOS publication step above, you’ll need to bump the version in gradle.properties:

LIBRARY_VERSION=0.1.1

Look for “KMMBridge Android and iOS Publish” in the GitHub Actions workflow list and run that instead of “KMMBridge iOS Publish” that we used earlier. That will publish iOS binaries, but also Android binaries.

The Android dependencies will publish to GitHub Packages. That is a maven repo, so you can add them from Gradle in your Android project.

Code Modules

The code in the sample is essentially the code in our KaMP Kit KMP sample app, but split out into an external “SDK” format. For your own code, you should replace the sample modules and packaging with whatever your project needs. The sample intends to show several libraries and a few basic features fully implemented.

analytics

This is a “typed” analytics library. The idea is that analytics is critical to many apps, but most analytics libraries are just “maps of strings” that marketing people put in a spreadsheet. Implementing and maintaining these values correctly across platforms is critical, but very difficult to verify. There’s also no “joy” for the developers. They never get to see the outcome. It’s just a tedious coding chore that nobody enjoys.

Having an analytics library with typed and named function calls is a method some teams use to improve analytics maintenance.

Because these functions are intended to be called directly from Swift, this whole module is exported to the Xcode framework. See allshared/build.gradle.kts:

cocoapods {
    ...
    framework {
        export(project(":analytics")) // <-- This line
        isStatic = true
    }
}

Only expose what you need

Usually your Swift code only needs to call a small subset of the KMP code directly. The rest is used internally by the library. You should minimize the API surface that is exported to Swift for several reasons. The simple rules to follow:

  • Don’t “export” modules unless you intend to call most of their public API directly
  • For modules exported to Swift, which includes the module creating the Xcode framework, mark Kotlin structures as “internal” if you don’t need to call them from Swift
  • Never transitively export unless you really need to, and you really never need to (so don’t)

breeds

This module provides the “data” of the app. It calls the Dog CEO REST API to get a list of dog breeds and images for those breeds. The module uses ktor for network calls, Sqldelight for local storage, and various other architectural structures to manage everything.

This module is not exported for a few reasons. The majority of the code is not called directly from Swift. The entry points are provided in allshared. Also, Sqldelight generates Kotlin code, which includes references to parts of the Sqldelight library that Swift definitely doesn’t need. There’s no way to make that generated code “internal”, so “hiding” it inside another module is a common approach to API surface hygiene.

allshared

This is the Xcode framework “umbrella” module. There is no Android code here. The build.gradle.kts file configures the Xcode framework structure, and the Kotlin code provides structured SDK initialization specific to iOS.

This is the module that KMMBridge is configured to publish from.

testapps

These are not intended to be “sample apps” for this tutorial. A helpful option for separate KMP repos is to have local test apps that you can run and build the KMP code directly with. It is not required to do this, but depending on your setup, you may find them useful. Both apps are configured to build the code locally.

To build and run the KMP code locally with your iOS app code, see CocoaPods Local Dev

✋ Check your build

Go back to the GitHub Actions page to check on your build. If done, continue to the next steps. If the build failed, here are some possible causes:

The GROUP value wasn’t changed, or the change wasn’t pushed. The build will fail at “Touchlab Sample Sanity Check”

“Sanity check error” screenshot Show how to find the Run workflow button

The build was run more than once If the release exists, the build will fail.

For other issues, reach out or file an issue in the quick start template repo.

👍 Next Steps

If the build succeeded, you should see a new release on your repo GitHub page.

GitHub release screenshot

Xcode Integration

Now that the build is complete you can try integrating it.

Go to touchlab/KMMBridgeCocoaPodsQuickStart-iOS and create another repo using that template (or just clone it, if you don’t plan on starting with it).

Artifact Authentication

GitHub requires you to authenticate to download binary files. This is true for public repos, and obviously true for private repos. Adding this authentication is not complex, but if any of the steps are incorrect, Xcode and CocoaPods aren’t very helpful explaining what went wrong. Make sure you double-check everything.

You can read about this in detail at Xcode and Binary File Authentication. We’ll summarize for this tutorial.

Create or open the file ~/.netrc. This provides “curl” and other tools with authentication information.

touch ~/.netrc
open ~/.netrc

Add the following to the file:

machine maven.pkg.github.com
  login <GitHub User>
  password <PAT>

The login is your GitHub username, and the password is the same PAT we created earlier. Save the file and exit.

GitHub requires auth for all binaries

You’ll see this point repeated in several places throughout the KMMBridge docs, but it is something that confuses people regularly.

GitHub Packages requires authentication for accessing files in all repos, public or private. To avoid forcing users to auth to GitHub, you’ll need to host your binaries somewhere else.

Adding the library

To add the library using CocoaPods first open the Podfile, and update the source with your Podspec repo.

platform :ios, '16'

source 'https://github.com/YOUR_ORG/YOUR_PODSPEC_REPO.git' # <- this line

target 'ios' do
  ...
end

Then after that run pod install from CI and wait until the operation succeeds. If there is a failure check your source url and that your artifact authentication is correctly setup.

You may notice in the Podfile that there’s a check for LOCAL_KOTLIN_PATH, this is for local development support. You can find out more in the documentation

Starting the project

Open ios.xcworkspace, either directly with Xcode or by opening it from a terminal.

To open from the terminal, cd to the directory you just cloned and run:

open ios.xcworkspace

In Xcode, make sure you have an iPhone simulator selected, then click the “Run” button.

Xcode Run screenshot

Android Publication

You can publish both iOS and Android binaries by running the “KMMBridge Android and iOS Publish” action in the GitHub Actions panel. With our template as it is currently configured, the Xcode XCFramework binaries are attached to the GitHub release that is created, while the Android dependencies are published to GitHub packages.

Very Important: First open gradle.properties and change LIBRARY_VERSION to 0.1.1. Add/commit/push in git.

Now go back to the “Actions” tab on the GitHub repo page, and run “KMMBridge Android and iOS Publish”.

This will also take around 15 minutes, or possibly more with the additional publications.

Clone the Android Sample

Open our Android sample repo. You can either use it as a template, or clone it directly.

While this repo references “SPM”, it should work for both SPM and CocoaPods.

Add the maven repo

In the Android sample project, open settings.gradle.kts. Find dependencyResolutionManagement at line 9. Change the uri to point to your KMP library repo:

dependencyResolutionManagement {
    val GITHUB_PACKAGES_USERNAME: String by settings
    val GITHUB_PACKAGES_PASSWORD: String by settings

    @Suppress("UnstableApiUsage")
    repositories {
        google()
        mavenCentral()
        maven {
            name = "GitHubPackages"
            url = uri("https://maven.pkg.github.com/touchlab/KMMBridgeSPMQuickStart") // <-- Change this
            credentials {
                username = GITHUB_PACKAGES_USERNAME
                password = GITHUB_PACKAGES_PASSWORD
            }
        }
    }
}

You’ll also need to supply GitHub auth info, similar to the way we did for Xcode. This is also true for public repos, for the same reasons.

Each user will need their own credentials. To do that, we reference Gradle properties in the build file. For each user, those values are set in the global Gradle properties file. Open or create ~/.gradle/gradle.properties. Your GitHub username is your actual username. The “password” is the PAT we created earlier:

GITHUB_PACKAGES_USERNAME=[GitHub username]
GITHUB_PACKAGES_PASSWORD=[Your PAT]

Add the new dependencies

Open gradle/libs.versions.toml. Find sharedlib-analytics on line 66. Change sharedlib-analytics and sharedlib-breeds to match the GROUP value from your KMP library.

sharedlib-analytics = { module = "[your GROUP]:analytics-android-debug", version.ref = "sharedlib" }
sharedlib-breeds = { module = "[your GROUP]:breeds-android-debug", version.ref = "sharedlib" }

Find sharedlib = on line 24. This is the version of your KMP library. Right now, that should be set to 0.1.1, but when you make future builds, this is what you’ll update.

sharedlib = "0.1.1"

If all is configured correctly, reload Gradle, and run the app!

Next Steps

The template is meant as a starting point. Rip out our sample code and add some of your own.

If you use a different CI, or need to publish to other locations, you can still use KMMBridge. It is meant to be easily extended. Be prepared, though, that any production build config can be complex, and Xcode builds are no exception. We’ve also found that the people setting up the KMP builds are usually familiar with Android and Gradle. Less so with Xcode and CocoaPods. We would highly encourage you to recruit help from your iOS team.