'KMM on iOS: There is no event loop. Use runBlocking { ... } to start one

I'm trying to use coroutines in a Kotlin Multiplatform project. I'm not experienced in either.

I'm trying to call this function

fun startFlow {
    coroutineScope.launch { 
        withContext(defaultDispatcher) {
           myFlow.collect { next -> onNext(next) }
        } 
    }
}

coroutineScope on iOS is this

val defaultScope: CoroutineScope = object : CoroutineScope {
    override val coroutineContext: CoroutineContext
        get() = SupervisorJob() + Dispatchers.Default
}

This is not the only call that gives me this problem, in fact all calls to coroutines seem to fail with this error:

kotlin.IllegalStateException: There is no event loop. Use runBlocking { ... } to start one.

This is how I import the library

val commonMain by getting {
        dependencies {
            implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3")
        }
    }

I'm using Kotlin 1.4.31. This problem is only present in iOS, Android works flawlessly.

I don't understand if I'm missing something.



Solution 1:[1]

New native concurrency model available for preview. Check out New memory model migration guide. native-mt suffix described below will no longer be needed after the release of this functionality along with Kotlin 1.7.0.


for iOS you need to use coroutines with suffix "native-mt", more info here

so replace your import with

implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2-native-mt")

Also note, that as per documentation:

When using other libraries that also depend on kotlinx.coroutines, such as Ktor, make sure to specify the multithreaded version of kotlinx-coroutines. You can do this with strictly:

implementation ("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2-native-mt"){
    version {
        strictly("1.5.2-native-mt")
    }
}

Solution 2:[2]

You can also use the regular coroutines library, but then you need to create a custom CoroutineDispatcher that posts the task on the mainRunLoop, e.g.:

object NSLooperDispatcher: CoroutineDispatcher() {
    override fun dispatch(context: CoroutineContext, block: Runnable) {
        NSRunLoop.mainRunLoop.performBlock {
            block.run()
        }
    }
}

// use custom dispatcher
withContext(NSLooperDispatcher) {
    myFlow.collect { next -> onNext(next) }
} 

There are problems with switching threads which the native-mt branch of coroutines is explicit about, so it may still be a good idea to use it. Otherwise, you are confined on one the main thread.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Stefan