Kotlin Multiplatform Mobile: Flow<List<*SomeModel*>> gets mapped to Flow<NSArray> in iOS

Viewed 989

My shared module contains Repository class which has two functions that return a list of items wrapped in a custom class extending Flow called CFlow.

I took the code for CFlow from kotlinconf-app and here:

fun <T> Flow<T>.asCFlow(): CFlow<T> = CFlow(this)

class CFlow<T>(private val origin: Flow<T>) : Flow<T> by origin {

    fun watch(block: (T) -> Unit): Closeable {
        val job = Job()

        onEach {
            block(it)
        }.launchIn(CoroutineScope(Dispatchers.Main + job))

        return object : Closeable {
            override fun close() {
                job.cancel()
            }
        }
    }
}

Repository example functions:

fun getData1(): CFlow<List<Profile>>

fun getData2(): CFlow<List<String>>

When I try to call this functions in iOS swift code the return type of the functions get converted to CFlow<NSArray> and inside of watch function the type of array is Any.

This is weird because in both kotlinconf-app and here the return types of functions are preserved and there is no casting involved in their codebase.

Question: How can I make the type of CFlow to be known in Xcode iOS project?

Android Studio version: 4.1.1

Kotlin lang and plugin version: 1.4.21

Kotlin Multiplatform Mobile plugin version: 0.2.0

Xcode version: 12.2

screenshot1

screenshot2

2 Answers

right now the compiler can't understand nested generics. as a workaround for now, wrap your list inside a data class like this

data class ProfileResult(val data: List<Profile>)

fun getData1(): CFlow<ProfileResult>

it will give you a concrete type in ios

This is because there are no generics in Objective-C. Arrays are ordered collections of objects.

So using any generic collection type in Kotlin, will lose it's type when translated to NSArray

I believe you have three options here:

  1. Wait for direct Kotlin - Swift interop (which is postponed currently)
  2. Cast values in Swift
  3. Don't use generics with collections. I'm personally not using the Flow wrapper currently and doing something like this:
fun observeItems(onChange: (List<Item>) -> Unit) {
        items.onEach {
            onChange(it)
        }.launchIn(coroutineScope)
    }

Exposing a dispose function to iOS

fun dispose() {
    coroutineScope.cancel()
}

And consuming like this:

repo.observeItems { items in
    ...
}

But definitely this is more work and hopefully these interop issues will be solved along the way

Related