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.

Creating a view state

link

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 );

Summing view states

link

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.

Default view states

link

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.

Adding a view state permanently

link

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.

Setting and removing a view state

link

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:

view.states.remove(MY_KEY)
ViewStateUtils.states(view).remove(MY_KEY);

Utility functions

link

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.