The render mechanism is implemented in the RainbowCakeViewModel
and RainbowCakeFragment
classes. Here’s the relevant part of RainbowCakeViewModel
(simplified):
abstract class RainbowCakeViewModel<VS : Any>(initialState: VS) : ViewModel() {
private val _state = MutableLiveData<VS>()
init {
_state.value = initialState
}
val state: LiveData<VS> = _state.distinct()
protected var viewState: VS
get() = _state.value!!
set(value) {
_state.value = value
}
// ...
}
_state
is a private backing property that holds the MutableLiveData
wrapping the state. This is never accessed directly, other than inside the initializer block that sets it to initialState
at construction time, which means that _state
never holds a null
value, it’s always initialized to a valid state.state
is a public property that exposes _state
through the read-only LiveData
interface for the RainbowCakeFragment
to observe. It also calls the distinct
extension on it, which prevents the LiveData
from emitting the exact same state twice (see here).viewState
is a convenience property that lets subclasses of RainbowCakeViewModel
read and write the current state without having to know that it’s stored in a LiveData
.This state is then trivially observed in the RainbowCakeFragment
’s onViewCreated
method, and delegated to the render
method that subclasses must override:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.state.observe(viewLifecycleOwner, Observer { viewState ->
viewState?.let { render(it) }
})
// ...
}
Fragments have two lifecycles that could be used to observe the LiveData
in the ViewModel.
For a more detailed discussion, read this recommended article about this topic: Fragment Lifecycles in the Age of Jetpack.
One of them is the lifecycle of the Fragment itself, which starts when the Fragment is created, and ends when it is destroyed. This starts with the onCreate
method, and ends with onDestroy
. To observe LiveData
with this lifecycle, the observation would have to start in onCreate
, and the Fragment instance itself would be passed in as a parameter.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.state.observe(this, Observer { ... })
}
The issue with using this lifecycle is that a Fragment’s view may be recreated any number of times during its lifetime, and any new views have to be populated with data again. However, the observer won’t be triggered after the creation of a new view, as LiveData
does not notify observers if the data they’re observing hasn’t changed. This would leave any new views uninitialized!
You could attempt to fix this by starting observation in the onViewCreated
method instead, like so:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.state.observe(this, Observer { ... })
}
Since you’d be creating a new Observer
instance each time the Fragment gets a new view, each of them would be invoked at least once, therefore properly populating the view. However, if you use the Fragment itself as the LifecycleOwner
, these observations will only be cleared up when the Fragment is destroyed. Until then, with every new view created, a new Observer
will be attached to the LiveData
, and when the state does change, all of them will be triggered one after another, running the render
method multiple times.
This shorter lifecycle of the Fragment’s view is also available to use with our observations. This can run its course multiple times over the lifecycle of a Fragment. It starts with onCreateView
/onViewCreated
, and it ends with onDestroyView
. This is the lifecycle actually used to observe view state in RainbowCake. The observe
call is made in the onViewCreated
method, and uses viewLifecycleOwner
as its first. This owner’s lifecycle will end in onDestroyView
, automatically cleaning up the observation.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.state.observe(viewLifecycleOwner, Observer { ... })
// ...
}
These issues are also discussed in this article and in this issue.