We often use RxJava for fetching network result, but usually have it tell us either Success or Error. How nice if it could also tell us Loading and perhaps Empty (nothing loaded) state as well, so we don’t need to handle that logic separately, and have it communicate to the View the State result.
I believe the RxJava code below is no longer stranger to anyone who has coded a network call.
service.fetchResult()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ data -> process(data) },
{ error -> report_error(error) }
)
It’s supercool, as it helps one to put the network request to the background easily, and handle the result or result easily.
A lot of times, we want something more than just Data or Error. While waiting for the fetching, we want to show Loading State on the UI. If the result comes back as empty result, we want to show Empty State.
In order to achieve that, we write more code surrounding the above Rx fetching code (within our presenter perhaps) to handle and update the UI accordingly.
How nice if we could do that all in RxJava, as per illustrate in the diagram below.
Thinking about it, we could possibly do that. Instead of handling all the logic in our presenter, we have use a model that represent all the state.
Here I defined it as below
data class UiStateModel (
private val inProgress: Boolean = false,
private val errorMessage: String? = null,
private val dataModel: DataModel? = null
) {
fun isLoading() = inProgress
fun isError() = errorMessage != null
fun isSuccess() =
dataModel?.dataString?.isNotEmpty() ?: false && !isError()
fun isEmpty() =
!inProgress && errorMessage == null && !isSuccess()
}
We could setup the Model that store inProgress flag, errorMessage and data. Based on these info, the model could be used to check if it is in Loading, Error, Success or Empty state.
To make this better, we could make the having some static class that instantiate UiStateModel per the state.
companion object {
fun loading() = UiStateModel(inProgress = true)
fun success(dataModel: DataModel)
= UiStateModel(dataModel = dataModel)
fun error(error: Throwable)
= UiStateModel(errorMessage = error.message)
}
So with this model now we have could represent the state, how we could combined to make use of it? Check out the code below that is used in the presenter.
service.fetchResult()
.map { data -> UiStateModel.success(data) }
.onErrorReturn
{ exception -> UiStateModel.error(exception) }
.startWith(UiStateModel.loading())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
uiState ->
when {
uiState.isLoading() -> view.isLoading()
uiState.isError() ->
view.isError(uiState.getErrorMessage())
uiState.isSuccess() ->
view.isSuccess(uiState.getData())
uiState.isEmpty() -> view.isEmpty()
else -> IllegalArgumentException("Invalid Response")
}
})
After fetching the result from the server, if it is successful, we convert it into a success UiStateModel. If it is error, we use onErrorReturn to convert it into an error UiStateModel.
The nice bit here is, on start, we could use startWith to convert it to loading UiStateModel.
We now have all the states specified, including empty State, which is handled internally within the UiStateModel to define it’s state.
I’ve made code example showing the flow in https://github.com/elye/demo_rxjava_manage_state
It shows all the states scenarios as below. Have fun!
The example here is a very simplified example as per shared by Jake Wharton. Check it out further for more insights.
Presentation: The State of Managing State with RxJava
Click “❤” below to share. Thanks! ~Twitter:elye; Facebook:elye