is there anyway to invoke suspending functions fro...
# android
m
is there anyway to invoke suspending functions from livedata observers? i’d like to be able to do something like this:
Copy code
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
You can launch from a scope or use a channel and pass data there
m
could you expand on that a bit?
a
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
Copy code
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
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
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
Alright, thanks. I don’t quite know how to do that but I think I can figure it out
g
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
Still doesn’t solve the issue that I don’t really want to launch a new coroutine from within the observer
g
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
Currently I’ve just been generating a new coroutine scope using
Copy code
val coroutineScope = CoroutineScope(<http://Dispatchers.IO|Dispatchers.IO>)
coroutineScope.launch { executeNetworkRequest }
Would using the viewmodel scope benefit me at all?
g
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
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
Copy code
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.