I think `Approach 1` is better because in option 1...
# android-architecture
s
I think
Approach 1
is better because in option 1 caller of interactor (which is viewmodel) don’t have to worry about which dispatcher the interactor will use. And interctor knows if it is doing network or some computation it knows about the context and which dispatcher it should use. While in
Approach 2
interactor is performing the operation in parentContext and the caller should know about the right context/dispatcher it should use before calling the function.
s
I like Approach 1 best. • It can be nice to know that all the code in the ViewModel only runs in the main-thread. • You can inject the Dispatchers.IO into your
Interactor
implementation and your ViewModel does not have to worry about it.
☝️ 1
(you could do away with the
ICoroutineContextProvider
and just inject the right
CoroutineContext
(i.e. Dispatchers.Main in your UI or Dispatchers.Unconfined or TestCoroutineDispatcher in your unit-tests). You can do the same for your
Interactor
implementation, injecting Dispatchers.IO instead.
✔️ 1
s
Thanks, I also think approach 1 is better because of same reason and I feel caller should not worry about finding the right context before calling the method. But I like to listen about other peoples views as well.
1
c
IMO a clean approach to coroutines means switching to the right context just before performing the operation so callers don't have to decide which context is right. So approach 1 is the way to go
👍 3
m
Approach 1 is better because the caller does not have to care about context switching. The code being called should take care of switching to a background context so that calling it will be main safe. Also I will use
viewModelScope
instead of creating my own custom scope.
👍 3
e
I agree with what everyone else already mentioned. 1 is the more correct approach imo.
👍 1
s
Copy code
class MyViewModel(
    private val interactor: Interactor
): ViewModel() {
    fun getSomething() {
        interactor.fetchSomething(
            viewModelScope
        ) {
            either(::handleError, ::handleData)
        }
    }

    private fun handleData(i: Int) { /* set/update LiveData */ }
    private fun handleError(error: Error) { /* set/update LiveData */ }
}
class Interactor(private val dispatchers: ICoroutineContextProvider) {
    fun fetchSomething(
        scope: CoroutineScope,
        callback: (result: Either<Error, Int>) -> Unit = {}
    ) {
        val job = scope.async(<http://dispatchers.io|dispatchers.io>) {
            //do something heavy

            Either.Result(5)
        }

        scope.launch(dispatchers.main) {
            callback(job.await())
        }
    }
}
what do you think about this approach?
s
I would keep the scope out of the Interactor.
fun getSomething() { viewModelScope.async { interactor.fetchSomething { …. } } }
And make the fetchSomething suspending:
suspend fun fetchSomething(callback: (Either<Error,Int>) -> Unit = {})
And you don’t need callbacks with suspend functions….
s
ok, thank you 👍
s
Copy code
class MyViewModel(
    private val interactor: Interactor
): ViewModel() {
    fun getSomething() {
        viewModelScope.launch{
            val result: Either<Error,Int> = interactor.fetchSomething()
            result.fold(::handleError, ::handleData)
        }
    }
    private fun handleData(i: Int) { /* set/update LiveData */ }
    private fun handleError(error: Error) { /* set/update LiveData */ }
}

class Interactor(private val dispatchers: ICoroutineContextProvider) {
    suspend fun fetchSomething(): Either<Error,Int> = coroutineScope {
        //do something parallel
        val intermediateResult = async { .... }
        
        //do something heavy
        ... 
        Either.Result(5)
    }
}
👍 1
(if
fetchSomething
does not need to do anything in parallel, you can skip the
= coroutine {
part)
Using Coroutines in your ViewModel: • Any method in your ViewModel that needs to do something async, just do
viewModelScope.launch { …. }
• Any business-logic/interactor method that needs to do something asynchronous (eg it would have used Rx or a callback without Coroutines), make that
suspend
and have it return the plain value. • As long as you are within a CoroutineScope (e.g. within a
launch
,
async
, etc), you can call the interactor’s
suspend
funs as if they return data immediately, as if they are not asynchronous, never blocking and return data from memory immediately.
👍 1
☝️ 2
s
got it, thank you 👍
s
@ciriti