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
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:
Its value
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
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
.