Search code examples
kotlincoroutinekotlinx.coroutines

Should be used a CoroutineScope's extension function or a suspending function


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:

  1. Should I declare 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?
  2. Should I just wrap a Repository#getRssChannel into a coroutineScope function in order to make all spawned suspending functions to be children of the latter?
  3. Or maybe it's already fine and I should change nothing. When to declare a function as a CoroutineScope's extension then?

Solution

  • 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.