I'm writing an app using coroutines (code below is greatly simplified). Recently I've watched Coroutines in Practice talk and got a little confused. Turns out I don't know when to use a CoroutineScope
's extension function and when to use a suspending function.
I have a mediator (Presenter/ViewModel/Controller/etc) that implements CoroutineScope:
class UiMediator : CoroutineScope {
private val lifecycleJob: Job = Job()
override val coroutineContext = lifecycleJob + CoroutineDispatchersProvider.MAIN
// cancel parent Job somewhere
fun getChannel() {
launch {
val channel = useCase.execute()
view.show(channel)
}
}
}
Business logic (Interactor/UseCase):
class UseCase {
suspend fun execute(): RssChannel = repository.getRssChannel()
}
And a repository:
class Repository {
suspend fun getRssChannel(): RssChannel {
// `getAllChannels` is a suspending fun that uses `withContext(IO)`
val channels = localStore.getAllChannels()
if (channels.isNotEmpty()) {
return channels[0]
}
// `fetchChannel` is a suspending fun that uses `suspendCancellableCoroutine`
// `saveChannel` is a suspending fun that uses `withContext(IO)`
return remoteStore.fetchChannel()
.also { localStore.saveChannel(it) }
}
}
So I have a few questions:
Repository#getRssChannel
as a CoroutineScope's extension function (because
it spawns new suspending functions: getAllChannels
,
fetchChannel
, saveChannel
)? How can I use it in the UseCase
then?Repository#getRssChannel
into a
coroutineScope
function in order to make all spawned suspending
functions to be children of the latter?CoroutineScope
's extension then?Answer to question 1:
No, you should not declare Repository#getRssChannel
as an extension function of CoroutineScope
, because you only invoke suspend functions but not start (launch
/ async
) new jobs. As @Francesc explained extension function of CoroutineScope
should only start new jobs, but cannot return immediatly result and should not be declared as suspend
by itself.
Answer to question 2:
No, you should not wrap Repository#getRssChannel
into a CoroutineScope
. Wrapping makes only sense if you start (launch
/ async
) new coroutines in this method. The new jobs would be children of the current job and the outer method will only return after all parallel jobs are finished. In your case you have sequential invocations of other suspending coroutines and there is no need of a new scope.
Answer to question 3:
Yes, you can stay with your code. If you would need the functionality of UiMediator#getChannel
more then once, then this method would be a candidate of an extension function for CoroutineScope
.