android10 / Android-CleanArchitecture-Kotlin

This is a movies sample app in Kotlin, which is part of a serie of blog posts I have written about architecting android application using different approaches.
https://fernandocejas.com/2018/05/07/architecting-android-reloaded/
4.69k stars 935 forks source link

How should we pass new CoroutineContext to UseCase #62

Open miroslavign opened 6 years ago

miroslavign commented 6 years ago

Or in general, what modifications should be implemented to support new CoroutineContext in UseCase and from calling side ?

Zhuinden commented 6 years ago

I'd assume all you need to do is make sure the Usecase function is suspend so that you can call it from any coroutine. Considering that ViewModel is typically the one who owns said CoroutineScope.

miroslavign commented 6 years ago

Let me try it like this,

I'm ok with ViewMmodel being the owner, the question is should it be like:

    abstract class UseCase<out Type, in Params> where Type : Any {

    abstract suspend fun run(params: Params): Either<Failure, Type>

    operator fun invoke(params: Params, coroutineDispatcher: CoroutineDispatcher, job: Job,
                        onResult: (Either<Failure, Type>) -> Unit = {}) {
        val scope = CoroutineScope(coroutineDispatcher + job)
        scope.launch {
            onResult(run(params))
        }
    }

    class None
    }

and from viewModel send/provide::

    private val job = Job()
    override val coroutineContext: CoroutineContext
        get() = job + Dispatchers.Main

    getBooks.invoke(GetBooks.Params("e-book-fiction"), Dispatchers.IO, job) { it.either(::handleError, ::handleBooks) }
patrykserek commented 5 years ago

@miroslavign I refactored the UseCase class like this:

abstract class UseCase<out Type, in Params> where Type : Any {

    abstract suspend fun run(params: Params): Result<Type>

    operator fun invoke(params: Params, job: Job, onResult: (Result<Type>) -> Unit = {}) {
        val backgroundJob = CoroutineScope(job + Dispatchers.IO).async { run(params) }
        CoroutineScope(job + Dispatchers.Main).launch { onResult(backgroundJob.await()) }
    }

    class None
}

I decided not to pass a CoroutineDispatcher as a method parameter. You can easily set a CoroutineScope based on a Job object, for example job + Dispatchers.Main. I'm not sure (I've not tested it yet) if this implementation would cancel the background task based on coroutine scope, but i cancel a job manually anyway inside OnCleared method of my base ViewModel in this way:

override fun onCleared() { job.cancel() super.onCleared() }

I also replaced Either with Result class (simpler implementation), but this solution should works for both cases.