Shahzad Aslam
11/19/2019, 11:19 PMApproach 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.streetsofboston
11/19/2019, 11:19 PMInteractor
implementation and your ViewModel does not have to worry about it.streetsofboston
11/19/2019, 11:21 PMICoroutineContextProvider
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.Shahzad Aslam
11/19/2019, 11:22 PMCan
11/20/2019, 8:31 AMmayojava
11/20/2019, 8:53 AMviewModelScope
instead of creating my own custom scope.efemoney
11/21/2019, 10:10 AMSergey B
11/21/2019, 6:00 PMclass 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())
}
}
}
Sergey B
11/21/2019, 6:00 PMstreetsofboston
11/21/2019, 6:15 PMfun getSomething() { viewModelScope.async { interactor.fetchSomething { …. } } }
And make the fetchSomething suspending: suspend fun fetchSomething(callback: (Either<Error,Int>) -> Unit = {})
streetsofboston
11/21/2019, 6:15 PMSergey B
11/21/2019, 6:18 PMstreetsofboston
11/21/2019, 6:20 PMclass 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)
}
}
streetsofboston
11/21/2019, 6:22 PMfetchSomething
does not need to do anything in parallel, you can skip the = coroutine {
part)streetsofboston
11/21/2019, 6:26 PMviewModelScope.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.Sergey B
11/21/2019, 6:29 PMShahzad Aslam
12/18/2019, 2:05 PM