Compose Multiplatform CI: Automating Jetpack Compose Projects

Compose Multiplatform simplifies cross-platform development by allowing you to share a single codebase for Android, iOS, desktop, and web applications using Kotlin and Jetpack Compose. Continuous Integration (CI) is essential for ensuring code quality and automating build, test, and deployment processes. This post will guide you through setting up CI for your Compose Multiplatform project.

What is Continuous Integration (CI)?

Continuous Integration (CI) is a software development practice where developers regularly merge code changes into a central repository, after which automated builds and tests are run. CI helps catch integration issues early, ensuring the codebase remains stable and reliable.

Why Use CI for Compose Multiplatform?

  • Automated Builds and Tests: Automatically build and test your application on multiple platforms with each code change.
  • Early Issue Detection: Identify and fix integration issues, compiler errors, and test failures early in the development cycle.
  • Consistent Environment: Ensure that builds are reproducible across different environments.
  • Faster Feedback Loop: Provides quick feedback on code changes, enabling faster iteration and development.

Setting Up CI for Compose Multiplatform

We’ll focus on using GitHub Actions, a popular CI/CD platform integrated with GitHub, to set up CI for a Compose Multiplatform project.

Step 1: Project Setup

Ensure your Compose Multiplatform project is set up correctly with shared code for different platforms. A typical structure might look like this:


MyComposeMultiplatformProject/
├── androidApp/        # Android application
├── iosApp/            # iOS application
├── desktopApp/        # Desktop application
├── jsApp/             # Web application
├── shared/            # Shared Kotlin code
└── gradle.properties

Step 2: Create a GitHub Actions Workflow

Create a new workflow file in .github/workflows directory of your repository. For example, .github/workflows/ci.yml.

Example Workflow for Android

This workflow builds and tests the Android part of your Compose Multiplatform project.


name: Android CI

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3
      - name: Set up JDK 17
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Grant execute permission for gradlew
        run: chmod +x gradlew

      - name: Build and test Android
        run: ./gradlew androidApp:assembleDebug androidApp:testDebugUnitTest
Explanation:
  • name: The name of the workflow.
  • on: Triggers the workflow on push and pull_request events for the main branch.
  • jobs: Defines the jobs to run.
  • runs-on: Specifies the type of machine to run the job on (Ubuntu).
  • steps: Defines the steps to execute in the job:
    • actions/checkout@v3: Checks out your repository to the runner.
    • actions/setup-java@v3: Sets up Java JDK 17.
    • Grant execute permission for gradlew: Makes the Gradle wrapper executable.
    • Build and test Android: Executes Gradle tasks to assemble the debug build and run unit tests.
Example Workflow for iOS

This workflow builds the iOS part of your Compose Multiplatform project. Building for iOS requires a macOS environment.


name: iOS CI

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

jobs:
  build:
    runs-on: macos-latest

    steps:
      - uses: actions/checkout@v3
      - name: Set up JDK 17
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Grant execute permission for gradlew
        run: chmod +x gradlew

      - name: Build iOS
        run: ./gradlew iosApp:assemble
Explanation:
  • runs-on: Specifies macos-latest because building for iOS requires a macOS environment.
  • Build iOS: Executes the Gradle task to assemble the iOS application.
Example Workflow for Desktop (JVM)

This workflow builds the desktop (JVM) application of your Compose Multiplatform project.


name: Desktop CI

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3
      - name: Set up JDK 17
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Grant execute permission for gradlew
        run: chmod +x gradlew

      - name: Build Desktop
        run: ./gradlew desktopApp:assemble
Example Workflow for Web (JS)

This workflow builds the web (JS) application of your Compose Multiplatform project. You will also want to run tests to make sure the builds are behaving as expected.


name: Web CI

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3
      - name: Set up JDK 17
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Install Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '16' # or the version you need

      - name: Grant execute permission for gradlew
        run: chmod +x gradlew

      - name: Build and Test Web
        run: ./gradlew jsApp:assemble jsApp:test

Step 3: Commit and Push to GitHub

Commit the workflow file(s) to your GitHub repository and push them to the main branch. GitHub Actions will automatically start running the workflow on each push and pull request.


git add .github/workflows/ci.yml
git commit -m "Add CI workflow"
git push origin main

Step 4: Monitor the Workflow

Go to your GitHub repository, click on the “Actions” tab, and you’ll see the status of your workflow. You can monitor the progress, view logs, and troubleshoot any issues that arise.

Tips for Effective CI

  • Use a Gradle Wrapper: Ensure that your project uses the Gradle Wrapper (gradlew) to provide a consistent Gradle version across different environments.
  • Cache Dependencies: Use caching to speed up the build process by reusing downloaded dependencies.
  • Parallel Builds: Configure Gradle to use parallel builds to leverage multiple cores and reduce build times.
  • Automated Testing: Integrate unit tests, UI tests, and integration tests into your CI pipeline.
  • Code Analysis: Include code analysis tools like ktlint or detekt to enforce code style and quality standards.
  • Dependency Management: Use dependency management tools to keep your dependencies up to date and secure.

Advanced Configurations

Here are some advanced configurations that you might find helpful.

Caching Dependencies

You can use GitHub Actions to cache Gradle dependencies and build outputs, significantly speeding up build times.


- name: Cache Gradle dependencies
  uses: actions/cache@v3
  with:
    path: |
      ~/.gradle/caches
      ~/.gradle/wrapper
    key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
    restore-keys: |
      ${{ runner.os }}-gradle-

Running Tests on Emulator/Simulator

For UI tests on Android and iOS, you can set up emulators/simulators in your CI environment.

Android Emulator Setup

- name: Set up Android emulator
  uses: android-actions/setup-android@v2

- name: Create and launch emulator
  run: |
    sdkmanager --install "system-images;android-30;google_apis;x86_64"
    echo "no" | avdmanager create avd -n test -k "system-images;android-30;google_apis;x86_64"
    emulator -avd test -no-window -noaudio &
    adb wait-for-device

Conclusion

Setting up Continuous Integration for your Compose Multiplatform project is crucial for maintaining code quality, automating builds, and ensuring faster development cycles. By using tools like GitHub Actions, you can easily build, test, and deploy your application across multiple platforms. Embrace CI to streamline your development workflow and deliver high-quality applications to your users.