-
Notifications
You must be signed in to change notification settings - Fork 36
4. States
A State is a central concept of this architecture. It represents what's displayed on the screen (or a part of the screen if the latter is composed of multiple ViewModels).
We can model States as either data class
es or sealed class
es. The former allows us to take advantage of the .copy()
function while creating a new State based on a previous one. However, when your State is very complex, it may be easier to model and maintain it as a sealed class
.
The note detail screen could contain the following State:
data class State(val note: Note? = null,
val isIdle: Boolean = false,
val isLoading: Boolean = false,
val isLoadError: Boolean = false,
val isNoteDeleted: Boolean = false,
val isDeleteError: Boolean = false) : BaseState
The note detail screen could contain the following State:
sealed class State {
data class NoteDetail(val note: Note) : State()
object Idle : State()
object Loading : State()
object LoadError : State()
object Deleted : State()
object DeleteError : State()
}
To handle Process Death, you'd need to make the States Parcelable
. Refer to the Handling Process Death section.
ViewModels in Roxie are designed to accept an initialState
in the constructor. If none is passed in, we are required to set it when the ViewModel is first initialized:
override val initialState = initialState ?: State(isIdle = true)
The Idle State is a special State necessary to "seed" the Reducer. This State is usually not emitted to the UI since it has no practical use to the user.
We can transform Changes into States using a chain similar to this one:
disposables += allChanges
.scan(initialState, reducer)
.filter { !it.isIdle }
.distinctUntilChanged()
.doOnNext { Timber.d("Received state: $it") }
.observeOn(AndroidSchedulers.mainThread())
.subscribe(state::setValue, Timber::e)
where the scan()
operator transforms new Changes into the new States. Note how we filter out the Idle state that the UI is not interested in. Note that the distinctUntilChanged()
operator prevents emitting the duplicate States one after another. We log the States emitted in doOnNext()
to make debugging easier. See the section on Logging for more info.
Once again, the block above showcases the power of RxJava. Quite a bit of work is done in a concise composable way.
Note that we use state::setValue
above. This is because this particular ViewModel handles Actions designed to produce multiple States. We could not use state::postValue in this case since the latter could potentially "lose" some of the intermediate States. The official documentation contains:
If you called this method multiple times before the main thread executed a posted task, only the last value would be dispatched.
If we were only emitting a single State from a ViewModel, we could replace:
.observeOn(AndroidSchedulers.mainThread())
.subscribe(state::setValue, Timber::e)
with a single line:
.subscribe(state::postValue, Timber::e)
Multiple Fragments could observe the same State. Just use the same ViewModel with the host Activity as the LifecycleOwner
. See an example here