The Model as the Form; Single Source of Truth bindings for Editable Text Fields in Android

Everything can be a list

TJ Dahunsi

May 23 2019 · 5 mins

Categories:
android
kotlin

In most mobile apps today, signing up for a service or creating any sort of resource requires a user to fill out a form of some kind, after which the information is marshaled and sent up to an API for processing. Before that happens however, some work has to be done to get the user’s input out of the form, into some serializable data structure. What if all this ceremony could be avoided though? What if the model was the form?

Consider an arbitrary User JSON blob representing the user of some service:

{ “firstName”:”Hugh”, “lastName”:”Maw”, “primaryEmail”:[email protected], “password”:”humor” }

On Android this may mean a ViewGroup with 4 EditTexts, and when the submit button is tapped, these EditTexts are queried for their contents, put into a User object, and then posted up. This process is subsequently repeated for any form that requires some text input, be it an event or team, and lends itself well to an abstraction: the same form may also be described a list of input fields, with each item in the list corresponding to a key in the eventual JSON payload. So, if we came up with a generic way to “itemize” any model object, we would have a list of items that all together constitute our payload. Taking this even further, what if every change in each item, would simultaneously change our model object so we wouldn’t have to query the EditTexts at the time we’re ready to submit? Seems rather doable, let’s begin.

We start off with the basic definition of an itemized slice of our model object, let’s call it Item. The definition for an Item class is:

public class Item { private @StringRes final int stringRes; private @Nullable final ValueChangeCallBack changeCallBack; private CharSequence value; Item(int stringRes, CharSequence value, @Nullable ValueChangeCallBack changeCallBack) { this.stringRes = stringRes; this.value = value; this.changeCallBack = changeCallBack; } }

Fundamentally, the item contains 2 pieces of information:

  1. Its value

  2. A callback invoked whenever its value changes.

Next, let’s define a User class that can be itemized:

public class User { .... public User(String id, String imageUrl, String screenName, String primaryEmail, CharSequence firstName, CharSequence lastName, CharSequence about) { .... } public List<Item<User>> asItems() { return Arrays.asList( new Item(R.string.first_name, firstName, this::setFirstName), new Item(R.string.last_name, lastName, this::setLastName), new Item(R.string.screen_name, screenName, this::setScreenName), new Item(R.string.email, primaryEmail, this::setPrimaryEmail), new Item(R.string.user_about, about, this::setAbout) ); }

The important thing to note here, is that the value change callback for each Item above, corresponds to method references for various fields of the User class, so whenever the value of an Item changes, the User class instance immediately reflects the new value.

So how do we use this? Well, since the User can now be itemized as a list, we can create a RecyclerView ViewHolder that can bind each Item to an EditText, listen to changes in the EditText, and modify our model without use having to worry about much else.

public class InputViewHolder extends RecyclerView.ViewHolder implements TextWatcher { // Implement text watcher for easy reference when unbinding protected Item item; protected final TextView hint; protected final EditText text; public InputViewHolder(View itemView) { super(itemView); hint = itemView.findViewById(R.id.hint); text = itemView.findViewById(R.id.input); } public void bind(Item item) { this.item = item; CharSequence newValue = item.getValue().toString(); CharSequence newHint = itemView.getContext().getString(item.getStringRes()); hint.setText(newHint); text.setText(newValue); text.addTextChangedListener(this); } public void unbind() { text.removeTextChangedListener(this); } @Override public void afterTextChanged(Editable editable) { item.setValue(editable.toString()); } @Override public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {/* Nothing */} @Override public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {/* Nothing */} }

With this, we have a 1 : 1 binding between each field in the User class, and an Item, and our form is wholly represented by the model. In other words, there is no separation between the User form UI representation and the User itself; the User model is the form.

This paradigm can be extended for further types on inputs as well. Say an input field had a limited set to choose from, tapping the EditText can show a dialog and when an option is selected, the Item is updated calling back and updating the User.

Itemized User and Team forms

In the above examples, all model objects are represented to the user as a list of Items which directly represent the state of the model in real time. There is a single source of truth, the model itself, so there isn’t a concern for synchronization between the UI and data layers; the binding is so tight, they’re one and the same. In the case of a JSON blob that has nested properties, you can still use the same abstraction, where each inner JSON object would be its own list section in the UI.

This paradigm lends itself to use outside models that are directly serialized over a network. Consider a UI that presents a set of search filters to a user; the options the user chooses will ultimately be broken up into distinct query parameters for some other request. If these query parameters were represented in their entirety by some encapsulating class that was “itemizable”, creating the query params would be as easy as traversing the list of Items while appending each Item to a properly formatted query String.

Itemized Search Filter Itemized Search Filter

Fundamentally, the real power here is being able to represent a model class as the sum of its individual fields via a list of Items and having a strong binding between the Items, the model and the UI. This eradicates the annoying problem of having disparate representations of state existing both in the model object and in the View itself; no more calling getText() on an EditText to get the current value of the user’s input.

In Android particularly, when combined with DiffUtil and its derivative DiffResult class, when a model object changes, the UI need only update the Items that changed, leading to an extremely responsive UI with minimal redraws. Even more, since the list of Items is more likely to be represented by a RecyclerView, the form now has great flexibility over different screen sizes and orientations, should it be combined with a GridLayoutManager with a dynamic span count. Lastly, when the new ViewBindings library is released, it’ll play nicely with the InputViewHolder, and the binding contract between an Item and its InputViewHolder becomes more apparent in code.

Source code for a project that uses the above paradigm extensively can be found below, particularly with its use with DiffUtil.

,