Coroutines, Flow & Concurrency
Async that works everywhere
KMP uses Kotlin coroutines for asynchronous work, and they run on every target. Your shared networking and database calls are suspend functions, and your shared logic launches them in coroutine scopes — the same code on Android and iOS.
suspend fun loadUser(id: Int): User {
return userApi.getUser(id) // suspends without blocking
}
Flow for streams of data
For values that change over time — a live database query, search results, a timer — use Flow. It’s multiplatform and pairs perfectly with shared repositories.
class UserRepository(private val api: UserApi) {
private val _users = MutableStateFlow<List<User>>(emptyList())
val users: StateFlow<List<User>> = _users
suspend fun refresh() {
_users.value = api.getUsers()
}
}
The iOS catch: consuming coroutines from Swift
Here’s the one genuinely tricky part of KMP. Android (Kotlin) consumes suspend functions and Flow naturally. Swift doesn’t understand them directly. There are a few solutions:
- Completion handlers — Kotlin/Native automatically exposes
suspendfunctions to Swift as functions with a completion callback (and modern versions support Swift’sasync/await). - SKIE — a popular tool that makes Kotlin
suspendfunctions andFlowfeel native in Swift (async/await and AsyncSequence). Highly recommended. - A wrapper — expose a simpler callback-based API from the shared module specifically for iOS.
// In Swift, with SKIE, a Kotlin suspend function feels native:
let users = try await userApi.getUsers()
Threading and Dispatchers
Coroutine Dispatchers work across platforms (Dispatchers.Default for CPU work, Dispatchers.IO on JVM). Keep heavy work off the main thread, and make sure UI updates happen on each platform’s main thread.
Structured concurrency still applies
Scopes, cancellation and structured concurrency work the same as in plain Kotlin. A shared ViewModel typically owns a scope that’s cancelled when the screen goes away — preventing leaks on both platforms.
Common mistakes
- Expecting Swift to call
suspend/Flowdirectly without a tool like SKIE. - Doing UI updates off the main thread.
- Leaking coroutine scopes instead of cancelling them with the screen.
Summary: Coroutines and Flow power async work and data streams in shared code on every platform. Android consumes them natively; on iOS, use Kotlin/Native’s async bridging or SKIE to makesuspend/Flowfeel native in Swift.