Binding Spinner in Android using DataBinding & InverseDataBinding 💫

Copyrights © Mobikul
Data binding is the process that establishes a connection between the application UI and business logic. If the binding has the correct settings and the data provides the proper notifications, then, when the data changes its value, the elements that are bound to the data reflect changes automatically. — Microsoft

A lot of jargon right? DataBinding essentially is a way to get and set attributes of a View in a layout. It takes away a lot of UI code away from the Activity or Fragment. If you’re new to DataBinding, follow the link to my presentation and come join the party 🎉

Consider we are building a Shopping App and should be able to update the quantity of the item from:

  1. The Cart screen (using Spinner)
  2. The Product screen (using FAB)

We’re devs and we love solving problems. So here’s a problem statement:

Cart with DataBinding

First, I’m changing the quantity 1 -> 2 using Spinner.

Second, I’m changing the quantity 2 -> 6 using Spinner.

Third, I use the FAB to add another item so my quantity changes as 6 -> 7 -> 8 -> 9.

Finally, on tapping the FAB once more, I’m prompted You cannot add more than 9 items.

Note how the item price (inside cart item) and the item count (outside cart item) changes with the quantity change.

There are multiple ways to approach this problem. I probably need a Spinner which takes an ArrayAdapter<Int> and returns an Int from onItemSelected. I’d be using SpinnerExtensions to add entries, get/set values and attaching listeners to the Spinner.

A. Binding Spinner with BindingAdapter

BindingAdapter is used to set any attribute to a View using @BindingAdapter annotation. For our Cart, we’ll set entries, onItemSelectedListener and newValue to the Spinner.

1. Create the ViewModel:

2. In your BindingAdapter:

  • The entries will be set using app:entries.
  • The newValue will be set using app:newValue.
  • The onItemSelectedListener will be set using app:onItemSelected.

3. In your XML, bind as follows:

Note app:onItemSelected lambda is invoked whenever user changes the item quantity manually.

4. Bind the Add to Cart FAB:

The logic of updating the cart can be seen in CartPresenterImpl. The corresponding view is CartActivity.

Caution ⚠️

As we’re setting newValue from XML, it calls onItemSelected again which can tangle the code in infinite loops. To circumvent this, we can set spinner.tag = position while setting the value and use a check inside the onItemSelected:

if (spinner.tag != position) {
listener.onItemSelected(parent.getItemAtPosition(position))
}

B. Binding Spinner with InverseBindingAdapter

InverseBindingAdapter is used to get any attribute from a View using @InverseBindingAdapter annotation. This helps us to achieve 2-way data binding for any View we want (including custom views). George Mount explains it very nicely in his blog Android Data Binding: 2-way Your Way. For our Cart, we’ll set entries and 2-way bind the selectedValue of the Spinner.

1. Create the ViewModel the same way we did before.

2. In your BindingAdapter:

  • The entries will be set using app:entries.
  • The selectedValue will be set using app:selectedValue.
  • The selectedValue will be bound (get) using app:selectedValue.

Note onItemSelected, inverseBindingListener.onChange() event is triggered. We name this event by suffixing AttrChanged with the dynamic attribute. In this case, as attribute name is selectedValue, the event name will be selectedValueAttrChanged.

InverseBindingAdapter is associated with a method used to retrieve the value from the View. In this case, onChange() triggers selectedValueAttrChanged event which pulls the selectedValue and notifies the ObservableField property quantity.

3. In your XML, bind as follow:

Note app:selectedValue="@={model.quantity}" is the 2-way binding. Whenever user changes the item quantity manually, selectedValue modifies themodel.quantity property.

We’re not using onItemSelected in XML (as selectedValue is bound 2 ways), we will have to add a callback to the model.quantity property to be able to push an event on the presenter too. This depends on the use-case. In our Cart, we do want to update the item count (outside cart item) so, lets do it:

4. Bind the Add to Cart FAB the same way as we did before.

The logic of updating the cart can be seen in InvCartPresenterImpl. The corresponding view is InvCartActivity.

And that’s pretty much it. The implementation can be extrapolated for say AutoCompleteTextView. DataBinding can be a pain at start but once you get the hang of it, it can benefit you in many ways. Thanks for reading. If you liked it, tap-tap-dance on the left. 🕺🏻

Happy Coding! 👨🏻‍💻👩🏻‍💻

Github Sample 👾

Also Try 👨🏻‍🍳

Reference 📚