View state
We will now introduce view states: a ViewState<V>
is special class that represents one property with its respective value for a view of type V
. A view state could be for example the text of a TextView
or its background color.
A view state can be set or unset to a view; an important property is that they do not leave side effects, i.e. adding a view sate and then removing it afterwards will leave the view in the same state as if it was never applied.
As usual we'll start from the most low-level functions. To create a view state for a single property we'll need to provide the following information:
- A key object that must be the same for all states that change the same property
- A field that contains our value
- Two functions: one that reads the value contained in the view (for restoring it later), and one that applies the value of the field
- Optionally we can also pass two functions that will be called when the view state is added and removed from the view.
In the following example we'll create a view state that changes the text of a TextView
:
createViewState<TextView, String>(KEY, movie.name
{ getText() }, //Reads the old value for later restoring. Receiver is TextView
{ setText(it) } //Given a movie name, sets it to the TextView. Receiver is TextView
)
ViewState.createViewState(KEY, movie.name,
tv -> tv.getText(), //Reads the old value for later restoring. Receiver is TextView
(tv, name) -> tv.setText(name) //Given a movie name, sets it to the TextView. Receiver is TextView
);
We can easily sum more view states and obtain a single bigger one. This is how it's done:
val big = vs1 + vs2 + vs3 + null + vs4
ViewState big = ViewStateUtils.sum(vs1, vs2, vs3, null, vs4);
As you can see from the example, summing null
is tolerated: it is simply interpreted as a view state that does nothing. After the sum big
is now a view state that encapsulates all the behaviors of the summed view states.
There are tens of utility functions for the most common view states. A few examples:
text("hello") //Creates a view state with the text "hello"
text(movie.name) //Creates a view state with the text binded to the field movie.name
paddingLeft(16)
visibility(View.GONE)
State.text("hello"); //Creates a view state with the text "hello"
State.text(movie.name); //Creates a view state with the text binded to the field movie.name
State.paddingLeft(16)
State.visibility(View.GONE)
A complete list of default view states can be found in the doc.
In order to add a view state to a view we can do the following:
view.states += text(movie.name) //the state can be a ViewState or also a Field<ViewState>
ViewStateUtils.states(view).add(State.text(movie.name)); //the state can be a ViewState or also a Field<ViewState>
If you do this you cannot later remove the state you've added, so be sure to call this function only when the state you set is permanent.
If you want to be able to add and remove view states dynamically you can do so with the function set()
. Once more you'll need to provide a key logically different from the key of a single view state. This concept is important because it's easy to view the two keys as the same logical value, but it's not the case.
When we create a view state we manage a single property and the key is bound to that property. Recall that a view state can represent the sum of multiple states, therefore managing multiple properties. The key that is required now is associated to the view state as a whole, possibly composed of multiple states.
Let's see this example to clarify:
class Info(
val text: String,
val hasErrors: Boolean
)
val MY_KEY = Any()
val f = mutableFieldOf(Info("Hello", false))
//When we set this state the view will display the text in white on a red background if it is in error
view.states.set(MY_KEY, f) {
text(it.text) + if (it.hasErrors) {
backgroundColor(Color.RED) + textColor(Color.WHITE)
} else null
}
//When we set this state the view will display the text on a green backround if it's not in error
//Since the key is the same as the previous one, all previous effects are removed
view.states.set(MY_KEY, f) {
text(it.text) + if (!it.hasErrors) {
backgroundColor(Color.GREEN)
} else null
}
class Info {
public final String text;
public final boolean hasErrors;
//Constructor omitted for brevity
}
Object MY_KEY = new Object();
MutableField<Info> f = MutableField.of(new Info("hello", false));
//When we set this state the view will display the text in white on a red background if it is in error
ViewStateUtils.states(view).set(MY_KEY, f, info -> {
return ViewStateUtils.sum(
State.text(info.text),
info.hasErrors ? ViewStateUtils.sum(State.backgroundColor(Color.RED), State.textColor(Color.WHITE)) : null
);
});
//When we set this state the view will display the text on a green backround if it's not in error
//Since the key is the same as the previous one, all previous effects are removed
ViewStateUtils.states(view).set(MY_KEY, f, info -> {
return ViewStateUtils.sum(
State.text(info.text),
!info.hasErrors ? State.backgroundColor(Color.GREEN) : null
);
});
To later remove a view state you can use the remove()
function:
Most of the times we'll just want to set a field to a view without thinking too much about it. For this reason there are several utility function to do just that, usually three per property: one that accepts a simple field that contains that value and two with the suffix ViewState
that accept a view state or a field of view state.
A few examples:
textView.setText(movie.name)
progressBar.setProgress(movie.progress)
ViewUtils.setText(textView, movie.name);
ViewUtils.setProgress(progressBar, movie.progress);
A complete list of these utility functions can be found in the doc.