I have been writting a lot of code that looks like this
class LoginViewModel : ViewModel() {
// A fragment observes this and shows/hides a progressbar
val loader = MutableLiveData<Boolean>()
fun fetchSomeData() = viewModelScope.launch {
loader.postValue(true)
// ...
loader.postValue(false)
}
}
is there a way to do it in a more clean way? if I have to do 100 requests then there will be 200 lines of loader.postValue
3 Answers 3
If I interpret it correctly, you can basically wrap everything in a single method, and pass it a lambda:
fun launchWithPost(code: suspend () -> Unit) {
viewModelScope.launch {
loader.postValue(true)
code.invoke()
loader.postValue(false)
}
}
fun fetchSomeData() = launchWithPost {
// ...
}
-
\$\begingroup\$ This looks good, but it wont be reusable in another screen, which will have different ViewModel \$\endgroup\$George Shalvashvili– George Shalvashvili2022年09月01日 19:17:52 +00:00Commented Sep 1, 2022 at 19:17
-
2\$\begingroup\$ @GeorgeShalvashvili You could make it an extention method:
CoroutineScope.launchWithPost
, or have both screens implement the same interface which specifies thatviewModelScope
andloader
should both be available, and then have it as an extension method on that interface. \$\endgroup\$Simon Forsberg– Simon Forsberg2022年09月02日 13:27:16 +00:00Commented Sep 2, 2022 at 13:27
Here is what I came up with:
fun Job.withLoader(state: MutableStateFlow<Boolean>) {
state.tryEmit(true)
invokeOnCompletion { state.tryEmit(false) }
}
fun Job.withLoader(state: MutableLiveData<Boolean>) {
state.postValue(true)
invokeOnCompletion { state.postValue(false) }
}
The usage:
fun authenticate(name: String, password: String) = viewModelScope.launch() {
authRepo.authenticate(
username = name,
password = password,
)
}.withLoader(loading)
I would leave handling the MutableLiveData<Boolean>
and launching a coroutine separate. That way it's not bound to ViewModel
at all. The best extension to add it to is the MutableLiveData
value itself. Also you are not doing any kind of error handling. Is it correct not to set the value back to false
on error?
fun fetchSomeData() = viewModelScope.launch {
loader.run {
//do your magic
}
}
suspend fun MutableLiveData<Boolean>.run(code: suspend () -> Unit) {
postValue(true)
try {
code.invoke()
} finally {
postValue(false)
}
}
-
\$\begingroup\$ Good call setting false in a finally block. I'd suggest a different name than
run
because it shadows the name of standard library top-level function, impacting readability. Another possible improvement is to replacepostValue()
withwithContext(Dispatchers.Main.immediate){ value = ... }
to avoid unnecessary deferring of the UI change by a frame. \$\endgroup\$Tenfour04– Tenfour042022年10月21日 18:22:26 +00:00Commented Oct 21, 2022 at 18:22
// ...
part in your code. (You can leave it as-is for now as your question has already been answered). As an example of a question with more context, see codereview.stackexchange.com/q/279291/31562 \$\endgroup\$