ubu
05/01/2020, 9:30 AMviewModelScope
from Android Architecture Components’ ViewModel
that i’ve been thinking over lately. I run a lot of background operations inside some ViewModel
, because I use its ViewModelScope
. These operations are use-cases
injected into constructor of this ViewModel
. For a better separation of concerns I would like to extract some of these operations in some other class that I would then inject in this ViewModel
, but in order to run these operations, I need that ViewModelScope
. Is there a way to provide it to injected components without passing the scope every time in some function signature?Erik
05/01/2020, 9:36 AMsuspend fun
, so the view model can call these from its viewModelScope
. Then there's no need to pass a CoroutineScope
, because these functions can only be called from a coroutine scope. Does this fit your case?uli
05/01/2020, 9:36 AMCoroutineScope
.ubu
05/01/2020, 9:39 AMCoroutineScope
abstract class BaseUseCase<out Type, in Params>(
private val context: CoroutineContext = <http://Dispatchers.IO|Dispatchers.IO>
) where Type : Any {
abstract suspend fun run(params: Params): Either<Throwable, Type>
open operator fun invoke(
scope: CoroutineScope,
params: Params,
onResult: (Either<Throwable, Type>) -> Unit = {}
) {
val job = scope.async(context) { run(params) }
scope.launch { onResult(job.await()) }
}
object None
}
Erik
05/01/2020, 9:39 AMubu
05/01/2020, 9:40 AMprivate fun proceedWithGettingAccount() {
getCurrentAccount.invoke(viewModelScope, BaseUseCase.None) { result ->
result.either(
fnL = { Timber.e(it, "Error while getting account") },
fnR = { account ->
_profile.postValue(ProfileView(name = account.name))
loadAvatarImage(account)
}
)
}
}
Erik
05/01/2020, 9:41 AMinvoke
operator could be a suspend fun
too, and it can return any type. It would then run asynchronously, depending on the scope's context, but you can switch context e.g. using withContext(context) { ... }
ubu
05/01/2020, 9:42 AMErik
05/01/2020, 9:43 AMfun CoroutineScope.myBuilder() = async { ... }
uli
05/01/2020, 9:53 AMprivate fun CoroutineScope.proceedWithGettingAccount() {
getCurrentAccount.invoke(BaseUseCase.None) {
...
}
}
fun CoroutineScope.getCurrentAccount(....
viewModelScope.proceedWithGettingAccount()
ubu
05/01/2020, 9:57 AMViewModel
, whereas I would like to abstract threading logic in my Domain
module.Erik
05/01/2020, 9:59 AMubu
05/01/2020, 10:03 AM/**
* Use-case for starting downloading files.
* @see Params
*/
class DownloadFile(
private val downloader: Downloader,
context: CoroutineContext
) : BaseUseCase<Unit, Params>(context) {
override suspend fun run(params: Params) = try {
downloader.download(
url = params.url,
name = params.name
).let { Either.Right(it) }
} catch (t: Throwable) {
Either.Left(t)
}
/**
* Params for downloading file.
* @property name file name
* @property url url of the file to download
*/
data class Params(
val name: String,
val url: Url
)
}
abstract class BaseUseCase<out Type, in Params>(
private val context: CoroutineContext = <http://Dispatchers.IO|Dispatchers.IO>
) where Type : Any {
abstract suspend fun run(params: Params): Either<Throwable, Type>
open operator fun invoke(
scope: CoroutineScope,
params: Params,
onResult: (Either<Throwable, Type>) -> Unit = {}
) {
val job = scope.async(context) { run(params) }
scope.launch { onResult(job.await()) }
}
object None
}
invocation:
downloadFile.invoke(
scope = viewModelScope,
params = DownloadFile.Params(
url = urlBuilder.file(file.hash),
name = file.name.orEmpty()
)
) { result ->
result.either(
fnL = { Timber.e(it, "Error while trying to download file: $file") },
fnR = { Timber.d("Started download file: $file") }
)
}
as you can see, execution context is injected, but vm calls method from BaseUseCase class.uli
05/01/2020, 10:32 AMopen operator fun invoke(
scope: CoroutineScope,
params: Params,
onResult: (Either<Throwable, Type>) -> Unit
Would become
open operator fun CoroutineScope.invoke(
params: Params,
onResult: (Either<Throwable, Type>) -> Unit
You can do that inside BaseUseCase
invoke
?
Instead you could also make it suspend and just return Either
.ubu
05/01/2020, 10:38 AMuli
05/01/2020, 11:06 AMinvoke
suspend, you can still simplify:
{
scope.launch { val result = withContext(context) { run(params) }
onResult(result) }
}
ubu
05/01/2020, 11:17 AMuli
05/01/2020, 11:39 AMopen suspend operator fun invoke(params: Params) : Either<Throwable, Type> = withContext(context) {
run(params)
}
viewModelScope.launch {
var result = downloadFile.invoke(
params = DownloadFile.Params(
url = urlBuilder.file(file.hash),
name = file.name.orEmpty()
)
)
result.either(
fnL = { Timber.e(it, "Error while trying to download file: $file") },
fnR = { Timber.d("Started download file: $file") }
)
}
ubu
05/01/2020, 1:56 PMCoroutineScope
extensions