Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Listen to Kotlin coroutine flow from iOS

I have setup a Kotlin Multiplatform project and attached a SQLDelight database to it. Its all setup and running correctly as i have tested it on the android side using the following:

commonMain:

    val backgroundColorFlow: Flow<Color> =
            dbQuery.getColorWithId(BGColor.id)
                    .asFlow()
                    .mapToOneNotNull()

which triggers fine in the Android projects MainActivity.kt using:

database.backgroundColorFlow.onEach { setBackgroundColor(it.hex) }.launchIn(lifecycleScope)

but when trying to access the same call in the iOS projects app delegate i get the following options and im unsure how to use them or convert them into my BGColor object:

database.backgroundColorFlow.collect(collector: T##Kotlinx_coroutines_coreFlowCollector, completionHandler: (KotlinUnit?, Error?) -> Void)

can anyone help me with how to use this?

like image 707
Wazza Avatar asked Oct 02 '20 16:10

Wazza


2 Answers

So this was resolved by creating a flow helper:

import io.ktor.utils.io.core.Closeable
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun <T> Flow<T>.asCommonFlow(): CommonFlow<T> = CommonFlow(this)
class CommonFlow<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()
            }
        }
    }
}

My backgroundColorFlow var is update as follows to utilise this helper:

    val backgroundColorFlow: CommonFlow<BGColor> =
            dbQuery.getColorWithId(BGColor.id)
                    .asFlow()
                    .mapToOneNotNull()
                    .map { BGColor(it.name) }
                    .asCommonFlow()

Then my swift works as follows:

database.backgroundColorFlow.watch { color in
            guard let colorHex = color?.hex else {
                return
            }
            self.colorBehaviourSubject.onNext(colorHex)
        }

and android like so:

database.backgroundColorFlow.watch { setBackgroundColor(it.hex) }

Hope this helps anyone that comes across this. I would like to convert the CommonFlow class into an extension of Flow but don't have the know-how atm so if any could that IMHO would be a much nicer solution

like image 138
Wazza Avatar answered Oct 31 '22 13:10

Wazza


You can do it in swift, with the mentioned collect method FlowCollector is a protocol which can be implemented to collect the data of the Flow object.

Generic example implementation could look like:

class Collector<T>: FlowCollector {

    let callback:(T) -> Void

    init(callback: @escaping (T) -> Void) {
        self.callback = callback
    }


    func emit(value: Any?, completionHandler: @escaping (KotlinUnit?, Error?) -> Void) {
        // do whatever you what with the emitted value
        callback(value as! T)

        // after you finished your work you need to call completionHandler to 
        // tell that you consumed the value and the next value can be consumed, 
        // otherwise you will not receive the next value
        //
        // i think first parameter can be always nil or KotlinUnit()
        // second parameter is for an error which occurred while consuming the value
        // passing an error object will throw a NSGenericException in kotlin code, which can be handled or your app will crash
        completionHandler(KotlinUnit(), nil) 
    }
}

The second part is calling the Flow.collect function

database.backgroundColorFlow.collect(collector: Collector<YourValueType> { yourValue in 
    // do what ever you want
}) { (unit, error) in 
    // code which is executed if the Flow object completed 
}

probably you also like to write some extension function to increase readability

like image 30
Marcel Lengert Avatar answered Oct 31 '22 13:10

Marcel Lengert