7

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.

David Corsalini
  • 7,958
  • 8
  • 41
  • 66

3 Answers3

16

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")
    }
}
Phil Dukhov
  • 67,741
  • 15
  • 184
  • 220
  • On the GitHub repository of coroutines it says that if you have the generic dependency on the common source set, it will import the platform specific versions. I though the suffix wasn't needed – David Corsalini Apr 03 '21 at 13:54
  • I have replaced a link with an official documentation one, it describes why do we still need to use it. Shortly - they're still working on it. Also there you'll find how to use strictly version in case when your other dependencies depends on coroutines – Phil Dukhov Apr 03 '21 at 14:03
  • Uncaught Kotlin exception: kotlin.native.IncorrectDereferenceException: illegal attempt to access non-shared @300b968 from other thread I'm getting this now after adding the strictly function. – Boldijar Paul Dec 08 '21 at 14:15
  • @BoldijarPaul this is an other issue. Check out how Kotlin Native concurrency model works in [documentation](https://kotlinlang.org/docs/native-concurrency.html). Shortly, you can't use `var` in your shared code (without atomic wrappers), but you can also face issues when you need to call `froze` when creating new objects. The code in your question doesn't seems to be causing such exception. You can update your question with more details/stacktrace, as well as relevant code, in case you won't find the answer in documentation. – Phil Dukhov Dec 08 '21 at 14:22
3

If you're on the latest version of KMM, and the above native-mt library doesn't work for you, then here's a solution for you -

  1. Go to your gradle.properties

  2. In #kotlin section (or on a new line), add the following line

    kotlin.native.binary.memoryModel=experimental

  3. Sync the gradle files

This should enable the experimental memory model for Kotlin and your project should start working on iOS.

Vedprakash Wagh
  • 3,595
  • 3
  • 12
  • 33
  • As of Kotlin Version `1.7.20`, the new memory manager is enabled by default, see https://kotlinlang.org/docs/native-memory-manager.html. Totally unrelated sidenote: that's why 'latest version' is not optimal, as no one knows which version was the 'latest' at the time of writing. So please be precise. No offense tho! – fklappan Feb 28 '23 at 11:08
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.

Stefan
  • 1,029
  • 9
  • 21