Efficient view updates¶
MVICore includes utility class to observe difference in the received models and prevent redundant view updates.
data class ViewModel(
val buttonText: String,
val buttonAction: () -> Unit,
val isLoading: Boolean
)
class View: Consumer<ViewModel> {
private val button: Button = ...
// Specify the fields to observe and actions to execute
private val watcher = modelWatcher<ViewModel> {
watch(ViewModel::buttonText) {
button.text = it
}
watch(ViewModel::buttonAction, diff = byRef()) {
button.setOnClickListener { it() }
}
}
override fun accept(model) {
// Pass the model
watcher.invoke(model)
}
}
By default, the difference is calculated by value (using equals
). It is configurable through diff
parameter.
The library also includes a couple of commonly used defaults.
val watcher = modelWatcher<Model> {
watch(Model::field, diff = byValue()) { } // Compare using equals (default strategy)
watch(Model::field, diff = byRef()) { } // Compare using referential equality
}
The difference can be observed on more than one field with custom diff strategy. For example, if the click listener should not be set when something is loading, you can do the following:
// Trigger when either loading flag or action changed
val loadingOrAction: DiffStrategy<ViewModel> = { p1, p2 ->
p1.isLoading != p2.isLoading || p1.buttonAction !== p2.buttonAction
}
val watcher = modelWatcher<ViewModel> {
watch({ it }, diff = loadingOrAction) { model ->
// Allow action only when not loading
button.setOnClickListener(
if (!model.isLoading) model.buttonAction else null
)
}
}
Models based on the sealed classes are supported with type
and objectType
functions.
sealed class Model {
data class A(val list: List<String>): Model()
object B : Model()
}
val watcher = modelWatcher<Model> {
type<A> {
watch(Model.A::list) { }
}
objectType<B> { modelB ->
}
}
Warning
Subsequent definitions of the same type will override previous ones.
If sealed class has a common property defined in the base class, its changes can be observed as well.
In the example below, Model::list
selector is triggered when the property is changed independently on model type.
sealed class Model {
abstract val list: List<String>
data class A(val list: List<String>): Model()
object B : Model() {
override val list: List<String> = emptyList()
}
}
val watcher = modelWatcher<Model> {
type<A> {
watch(Model.A::list) {
// Property of Model.A only
}
}
watch(Model::list) {
// Common property
}
}
The watcher also provides an optional DSL to add more clarity to the definitions:
val watcher = modelWatcher<ViewModel> {
// Method call
watch(ViewModel::buttonText) {
button.text = it
}
// DSL
ViewModel::buttonText {
button.text = it
}
}
val watcher = modelWatcher<ViewModel> {
// Method call
watch(Model::buttonAction, diff = byRef()) { }
// DSL
val byRef = byRef<() -> Unit>()
ViewModel::buttonAction using byRef {
}
}
val watcher = modelWatcher<ViewModel> {
// Method call
val loadingOrAction: DiffStrategy<ViewModel> = { p1, p2 ->
p1.isLoading != p2.isLoading || p1.buttonAction !== p2.buttonAction
}
watch({ it }, loadingOrAction) {
}
// DSL
(ViewModel::isLoading or ViewModel::buttonAction) {
}
}