https://kotlinlang.org logo
Title
m

matt tighe

01/18/2019, 5:41 PM
is there anyway to invoke suspending functions from livedata observers? i’d like to be able to do something like this:
val liveData: LiveData<List<Thing>> = myDao.getAllThings()
val mediator = MediatorLiveData<Things>

fun startObservation() {
    mediator.addSource(liveData) { it?.let { newThings->
        sendThingsOverNetwork(newThings)
    }
}

suspend fun sendThingsOverNetwork = withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
     // Do network ops off main thread
}
a

Allan Wang

01/18/2019, 5:42 PM
You can launch from a scope or use a channel and pass data there
m

matt tighe

01/18/2019, 5:44 PM
could you expand on that a bit?
a

Allan Wang

01/18/2019, 5:47 PM
you can do
scope.launch { sendThingsOverNetwork(newThings) }
, where scope is your current scope. The problem is that you might be dealing with backpressure, in that the data you observe occurs faster than you can handle it. In that case, you can use a channel to queue requests, or use a conflated channel to only keep the latest request while you handle previous ones. You would start handling the channel using
launch {
    for (thing in channel) {
        ...
    }
}
And you can send 'things' to channel via
offer
or
send
, depending on your needs
One benefit of livedata against channels is that it automatically preserves data on configuration changes. If you don't need that, you can consider just using channel when passing your data.
m

matt tighe

01/18/2019, 5:50 PM
I do need that. It’s all stateful data at the viewmodel layer. I’ve been trying to avoid launching coroutines from the viewmodel because it makes testing hard. But also doesn’t
scope
need to be coroutine scope? It doesn’t seem like there is a coroutine scope in observers
a

Allan Wang

01/18/2019, 5:51 PM
You can build a scope around the same lifecycle you are using for your livedata. Ie create a new job on create/start, and cancel it on destroy/stop
m

matt tighe

01/18/2019, 6:00 PM
Alright, thanks. I don’t quite know how to do that but I think I can figure it out
g

ghedeon

01/18/2019, 7:48 PM
coroutine scope is totally fine in ViewModel. It's actually where it belongs and seems like it's going to be part of the ktx library: https://android.googlesource.com/platform/frameworks/support/+/androidx-master-dev/lifecycle/viewmodel/ktx/src/main/java/androidx/lifecycle/ViewModel.kt
m

matt tighe

01/18/2019, 8:09 PM
Still doesn’t solve the issue that I don’t really want to launch a new coroutine from within the observer
g

ghedeon

01/18/2019, 8:33 PM
well, you want to execute suspended function from there, what do you expect? 🙂 You can also pass the scope from viewmodel to the "whatever thing executes your network request" and launch the coroutine there.
m

matt tighe

01/18/2019, 8:48 PM
Currently I’ve just been generating a new coroutine scope using
val coroutineScope = CoroutineScope(<http://Dispatchers.IO|Dispatchers.IO>)
coroutineScope.launch { executeNetworkRequest }
Would using the viewmodel scope benefit me at all?
g

ghedeon

01/18/2019, 9:09 PM
the idea of viewmodel's scope is that its Job is nicely canceled on
onCleared
. It all makes sense if you think about it, with lifecycle and stuff.
m

matt tighe

01/18/2019, 9:21 PM
yeah, I should probably do that in any case then. my underlying issue though is with verification in testing right now. in reality my code looks like this
init {
    mediator.addSource(source) {
        callLaunchingFunction()
    }
}

public fun submitEvent() {
    callLaunchingFunction()
}

private fun callLaunchingFunction() {
    val coroutineScope = CoroutineScope(<http://Dispatchers.IO|Dispatchers.IO>)
    coroutineScope.launch { myObject.suspendingFunction() }
}
Trying to use mockito to verify
myObject.suspendingFunction()
will fail randomly, because I haven’t found a way to guarantee the launch of the coroutine in
callLaunchingFunction()
yet. Turning it into a suspending function and launching from a higher level would work in the case of calling
submitEvent
but doesn’t solve the issue of when the observer calls it.