KSKaran Sharma
← All posts
Jun 2025 · 5 min read

Compose Multiplatform to the City - KRAIL

Hello from Sydney! In this post, I'll walk you through how I used Compose Multiplatform to build a real world, cross-platform public transport app called KRAIL for Android and iOS. This project was part of my talk at Google I/O Extended 2025 (Brisbane).

KRAIL App is available on both Google Play and Apple App Store.

Goal

I wanted to build an app that:

Why Compose Multiplatform?

JetBrains Compose Multiplatform is a modern framework that allows you to write declarative UI once in Kotlin and run it on Android, iOS, desktop, and even web. The shared code lives in the commonMain module, while platform-specific code goes in androidMain and iosMain.

This project used:

Project Structure Breakdown

The Android and iOS apps both call into KrailApp(), our main Compose entry point, with platform-specific setup code for DI using Koin.

Android Compilation Flow

iOS Compilation Flow

Networking with Ktor

For making HTTP requests, I used Ktor, a Kotlin-native library. It supports multiple platforms via engine abstraction:

I define a shared expect function for the HTTP client, and provide actual implementations per platform.

// Shared
expect fun httpClient(): HttpClient

// Android
actual fun httpClient(): HttpClient = HttpClient(OkHttp) { ... }

// iOS
actual fun httpClient(): HttpClient = HttpClient(Darwin) { ... }

We can use ContentNegotiation with either JSON or Protobuf depending on the backend, and Ktor handles deserialization into data classes. We can use the kotlinx.serialization library for serialization / deserialization purposes.

Local Storage with SQLDelight

In order to save frequent trips, we need to use a multiplatform library for storage and I chose SQLDelight for this. It works well for both Android and iOS. SQLDelight stands out because it generates type-safe Kotlin APIs directly from .sq SQL files, ensuring compile-time safety and reducing runtime errors. Room also got multiplatform support recently, however, here are some reasons why I prefer SQLDelight.

Code example: write your SQL in a .sq file:

insertOrReplaceTrip:
INSERT OR REPLACE INTO SavedTrip(
    tripId,
    fromStopId,
    fromStopName,
    toStopId,
    toStopName,
    timestamp
)
VALUES (?, ?, ?, ?, ?, datetime('now'));

SQLDelight generates the Kotlin API for us, so we can get the queries object from the Database object and directly execute the SQL statement using Kotlin code.

private val db = Database(factory.createDriver())
private val query = db.databaseQueries

query.insertOrReplaceTrip(
    tripId,
    fromStopId,
    fromStopName,
    toStopId,
    toStopName,
)

The correct driver is provided per platform, ensuring smooth multiplatform support.

Theming and Design System

I intentionally chose not to use the Material 3 library. Instead, I built the TAJ design system directly on top of Compose's foundational APIs. For example, instead of using the Material TextField, TAJ uses the low-level BasicTextField to create custom input components.

The core of TAJ consists of KrailColors and KrailTypography, which define the color palette and typography styles for the app. These are provided to composables via CompositionLocalProvider, enabling consistent theming and automatic support for light and dark modes. This approach gives full control over the UI, avoids dependencies on evolving libraries, and ensures a consistent look and feel across platforms:

All are injected into the composables via CompositionLocalProvider, and support light/dark themes automatically.

Crash Monitoring & Analytics

To improve reliability and product insights:

We also tracked:

This data helped optimize the app and prioritize features.

Live Demo

I wrapped up the session with a demo across:

The app KRAIL is open-source and available on GitHub.

Talk Recording

Slides

Conclusion

Compose Multiplatform has evolved quickly, and it's ready for real-world apps. With strong tooling, predictable compilation, and first-class multiplatform libraries like Ktor and SQLDelight, it's an exciting time to build UIs for every screen using just Kotlin.

Whether you're a solo dev or scaling for teams, you can now design, code, and ship delightful cross-platform apps with one shared mindset and language.