· 6 min read Posted by Gustavo Fão Valvassori

Fastlane in Kotlin Multiplatform projects

Fastlane is a great tool for automating your mobile app tasks. In this post, we will show how to use Fastlane in a Kotlin Multiplatform project.
Taylor Vick - https://unsplash.com/pt-br/@tvick?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash
Credit: Taylor Vick - https://unsplash.com/pt-br/@tvick?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash

Fastlane is a tool for mobile development (iOS and Android) that allows you to automate tasks such as building, testing, and deploying your app. It can save you a lot of time and effort, especially with tedious tasks.

As a Kotlin Multiplatform Developer, this is especially helpful as it centralize all your build steps in a single place. If your project has multiple modules, things can get out of hand easy. And with Fastlane, you can orchestrate all the build steps and call only one command, like we will see in the ‘test’ command below.

It uses Ruby scripts to define the tasks (here known as lanes) that can be called from the command line. To start using it, all you need to do is install it (using Brew or RubyGems), and then create a Fastfile in the fastlane directory.

Fastlane and KMP

When you start reading the docs, you will find out that most examples are pure native, or using other multiplatform frameworks like React Native or Flutter. So, how can we use Fastlane in a Kotlin Multiplatform project?

The answer is simple: it depends.

For Android Development, KMP is just another Android library. In other words, you will use fastlane as you would in any other Android project.

But for iOS, things are a bit different. Since KMP does manage the native apps (like React Native or Flutter), depending on how you are structuring your project, you may have a different solution.

If your iOS project is integrated through XCode Build Phases or Cocoapods, you can use Fastlane as you would in any other iOS project. But if your KMP exports a framework that is linked to the iOS project (like when you use KMMBridge), you will need to first build it using the gradle command.

Creating a Fastfile

As we previously mentioned, the Fastfile is where you define the lanes that Fastlane will execute. Each lane is composed by a series of actions that will be executed in order. Those tasks can be pre-defined by Fastlane, created by Plugins, or custom scripts that you write.

So everything starts by creating the Fastfile. If your project supports both Android and iOS, your initial configuration, can be something like this:

platform :ios do
  lane :build do
    puts "Building iOS app"
  end
end

platform :android do
  lane :build do
    puts "Building Android app"
  end
end

Here we are defining two lanes, one for iOS and one for Android, that will print a message. To call it, you can run fastlane $PLATFORM $LANE (or bundle exec fastlane $PLATFORM $LANE) in the terminal. Note that as we define multiple platforms, we need to specify the platform we want to run before the lane name.

$ fastlane android build
$ fastlane ios build

Android

For Android, the lanes are pretty straightforward. You can use the built-in gradle action to build, test, and deploy your app.

platform :android do
  lane :build do
    gradle(
      task: "assemble",
      build_type: ENV['releaseBuild'] == 'true' ? "Release" : "Debug"
    )
  end
end

If you need to run tests, or build different flavors, you can simply tweak the gradle action parameters. A simple test lane could be as simple as:

platform :android do
  lane :build do
    gradle(
      task: "testDebugUnitTest"
    )
  end
end

iOS

For iOS, things are a bit different, but still simple. To build the XCode project, you can use the build_app (or its alias, gym) action. This action uses the xcodebuild command line to assemble the app.

For tests, you will need to follow a similar approach from the iOS build, but using the run_tests (or its alias scan) action.

It’s important to note that you need to have the XCode command line tools installed. For more details, check the Fastlane iOS getting started docs.

About how to really build the app, everything depends on how your KMP is integrated with the iOS project.

Build Phases

If you created your app using the JetBrains KMP Wizard, you are probably using Build Phases. To compile your iOS app, you should call the build_app action.

platform :ios do
  lane :build do
    build_app(
      project: './iosApp/iosApp.xcodeproj',
      configuration: ENV['releaseBuild'] == 'true' ? 'Release' : 'Debug',
      scheme: 'iosApp',
      archive_path: './build/archives/iOS',
      buildlog_path: './build/archives/logs/',
      destination: 'generic/platform=iOS'
    )
  end
end

For tests, you need to run them for both the Kotlin/Gradle project, and the iOS project. So your lane will have at least two calls:

platform :ios do
  lane :test do
    gradle(
      tasks: [
        ':composeApp:iosSimulatorArm64Test',
        ':composeApp:iosX64Test',
      ]
    )

    run_tests(
      project: './iosApp/iosApp.xcodeproj',
      configuration: ENV['releaseBuild'] == 'true' ? 'Release' : 'Debug',
      scheme: 'iosAppUiTest',
      device: 'iPhone 16 (18.2)',
      ensure_devices_found: true,
      prelaunch_simulator: true,
      fail_build: false,
      reset_simulator: true
    )
  end
end

KMMBridge or XCFramework

If you build the XCFramework from gradle and consume it (like when you do SPM local dev with KMM Bridge), all you need to change, is add the gradle action from your iOS platform lanes:

platform :ios do
  lane :build do
    buildMode = ENV['releaseBuild'] == 'true' ? 'Release' : 'Debug'

    # Compile the Framework
    gradle(
      tasks: [":composeApp:assembleComposeApp#{buildMode}XCFramework"]
      # tasks: [":composeApp:spmDevBuild"] # Sample using SPM Dev Build from KMMBridge
    )

    build_app(
      project: './iosApp/iosApp.xcodeproj',
      configuration: buildMode,
      scheme: 'iosApp',
      archive_path: './build/archives/iOS',
      buildlog_path: './build/archives/logs/',
      destination: 'generic/platform=iOS'
    )
  end

  lane :test do
    gradle(
      tasks: [
        ':composeApp:iosSimulatorArm64Test',
        ':composeApp:iosX64Test',
      ]
    )

    # Compile the Framework
    gradle(
      tasks: [":composeApp:assembleComposeApp#{buildMode}XCFramework"]
      # tasks: [":composeApp:spmDevBuild"] # Sample using SPM Dev Build from KMMBridge
    )

    run_tests(
      project: './iosApp/iosApp.xcodeproj',
      configuration: ENV['releaseBuild'] == 'true' ? 'Release' : 'Debug',
      scheme: 'iosAppUiTest',
      device: 'iPhone 16 (18.2)',
      ensure_devices_found: true,
      prelaunch_simulator: true,
      fail_build: false,
      reset_simulator: true
    )
  end
end

Final Thoughts

Fastlane is a powerful tool that can save you a lot of time and effort. It’s easy to use, and you can start with simple lanes and then add more complex tasks as you need.

This tool can be an essential part of your app structure, especially if you are working with a multiplatform projects, and different CI/CD environments. Usually, each CI env has its own way of doing stuff, and having it set-up in a Fastfile can make your life easier to migrate/reuse.

If you want to check a simple example, you can check the Fastlane KMP Sample.

KMP Pipeline Automation and Optimization

Navigating build systems and optimizing your CI/CD pipeline can be challenging. Clients leverage Touchlab’s experience to solve their KMP pipeline challenges and optimize other team development workflows.