Have a short question here. The usage of Courotine...
# android-architecture
a
Have a short question here. The usage of Courotines inside UseCase is bad practice?
g
Why? Bring coroutines all the way up to ViewModel and subscribe there.
a
And what about passing coroutine scope to UseCase?
g
Sometimes it makes sense, if the caller is still in Java for example. But it complicates passing the result back to VM. It's asynchronous, so you'd need a callback or LiveData/Rx/Flow.
s
I’d never rely on use cases deciding where to be executed and rather let the callers choose the context where they want to execute stuff. That way you avoid having sync/async use cases
i
this is my usecase class:
Copy code
abstract class SuspendUseCase<out Type, in Params> where Type : Any {

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

    operator fun invoke(scope: CoroutineScope, params: Params, onResult: (Either<Failure, Type>) -> Unit = {}) {
        val job = scope.async { run(params) }
        scope.launch(Dispatchers.Main) { onResult(job.await()) }
    }

    class None
}
how can this be furthered improved? Or there should be different suspend usecase classes for IO, Compute and non-blocking usecases?
a
Passing result back can be done by Callback/LiveData/Rx - question is more about philosophy :)
u
@ghedeon how would you then have action that doesnt get canceled on viewmodel getting killed?
g
That would be responsibility of the scope that owns the lifecycle of that action.
u
true, but how do you make it work if usecase.execute() is a plain suspend function without explicit scope? (i.e in caller scope)
g
that doesn't mean that it's all executed in caller scope tho. You can easily have
usecase() -> foo.bar()
where
bar
is called on whatever scope was injected into
foo
.
kind of similar to launching a separate
CoroutineScope().launch{}
inside of suspend function (just not structured in this case).
u
so you'd inject appScope instance into the use case?
g
Depends on case. This is "structured" concurrency, structure hierarchy of scopes depending on use case and required semantics
👍 1
u
Yes but im still unsure about scope as a parameter, shouldnt it be a impl. detail of such component?
g
If it is implementation detail of such component, it should have lifecycle of this component too. If scope lives longer than the component, than it should be a constructor param. If component doesn't have own lifecycle, it just should be suspend function (or a function with scope as receicer/argument) in component, so it will use caller scope instead
1
u
@gildor If you pass it in as ctor param, to a component that has a shorter lifecycle than the scope instance passed in, isnt that a memory leak?
also, im not very sure about it. one precedence for it already is in view - viewModel. And view doesnt have viewModel scope passed in, and nobody complains
g
It's not necessary memory leak, but you can create one, depends on what you do in such coroutine, it's true. Suspend functions is safest way for sure
u
well if appScope is injected into viewModel, afaik then if used it will hold reference ti the viewModel until suspend function completes, even when viewModel's onCleared was called
g
No, it's incorrect, it will hold reference only if you use viewModel members in this coroutine after suspend But as we already discussed with you previously. Im not advocating for this approach, especially for components which already have own lifecycle and even own scope out of the box. Pretty sure in 99% cases you should just use viewModel coroutineScope for background tasks. If you need tasks that run longer than viewModel... Than do not add them to view model