Search code examples
androidioskotlinkotlin-multiplatformkotlin-flow

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


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


Solution

  • 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