Search code examples
androidandroid-recyclerviewandroid-databindingobservablelistandroid-binding-adapter

Android data binding ObservableList behaviour issue


I find it hard to spot the real raison d'etre of the android.databinding.ObservableList as a data binding feature.

At first it looked like a cool tool to display lists, through data binding, adding them via xml to a RecyclerView. To do so, I made a BindingAdapter like this:

@BindingAdapter(value = {"items"}, requireAll = false)
public static void setMyAdapterItems(RecyclerView view, ObservableList <T> items) {
    if(items != null && (view.getAdapter() instanceof MyAdapter)) {
        ((GenericAdapter<T>) view.getAdapter()).setItems(items);
    }
}

This way, I can use the attribute app:items in a RecyclerView with a MyAdapter set to it, to update its items.

Now the best feature of ObservableList is you can add an OnListChangedCallback to it, which handles the same events available in the RecyclerView to add/move/remove/change items in it without actually reloading the whole list.

So the logic I thought to implement was the fallowing:

  1. I start with an empty MyAdapter
  2. When my items are fetched from my APIs, I instantiate an ObservableArrayList wrapping them and pass it to the binding
  3. Data binding invokes my BindingAdapter passing the items to MyAdapter
  4. When MyAdapter receives new items, it clears its old ones and adds an OnListChangedCallback to the ObservableList received to handle micro-changes
  5. If anything changes in the ObservableList, MyAdapter will change accordingly without refreshing completely
  6. If i want to display a completely different set of the same items type, I can just re-set the binding variable, so the BindingAdapter will be invoked again and MyAdapter items will be completely changed.

For example, if I want to display items of type Game which I have two different lists for: "owned games" and "wishlist games", I could just call binding.setItems(whateverItems) to completely refresh the displayed items, but for example, if I move the "wishlist games" around the list to organize them by relevance, only micro-changes will be executed within each list without refreshing the whole thing.

Turns out this idea was unfeasible because data binding re-executes the BindingAdapter every time a single change is made to an ObservableList, so for example I observe the fallowing behaviour:

  1. I start with an empty MyAdapter
  2. When my items are fetched from my APIs, I instantiate an ObservableArrayList wrapping them and pass it to the binding
  3. Data binding invokes my BindingAdapter passing the items to MyAdapter
  4. When MyAdapter receives new items, it clears its old ones and adds an OnListChangedCallback to the ObservableList received to handle micro-changes
  5. If anything changes in the ObservableList, the BindingAdapter is invoked again, thus MyAdapter receives the whole list again and completely refreshes.

This behaviour seems quite broken to me because prevents the ObservableList from being usable within an data-bound xml. I cannot seriously figure out a legit case in which this behaviour is desirable.

I looked up some examples: here and this other SO question

In the first link all the examples used the ObservableList directly to the Adapter without even passing form xml and actual data binding, while in the code linked in the SO answer, the developer did basically the same thing I tried to do, adding:

if (this.items == items){
        return;
}

at the beginning of his Adapter.setItems(ObservableList<T> items) to discard all the cases where the method is invoked because of simple changes in the ObservableList.

What is the need of this behaviour? What might be some cases where this behaviour is desirable? I feel like ObservableList is a feature added with data binding and is really useful except when used with actual data binding, in which case it forces you to defend from its behaviour. If I declare it as a simple List in both xml data tags and in BindingAdapter signature, then I can cast it back to ObservableList inside MyAdapter and it works just fine, but this is quite a bad hack. If it was just a separate feature from data binding, without triggering the binding at every change it would have been much better in my opinion.


Solution

  • According to the example provided in the docs https://developer.android.com/topic/libraries/data-binding/index.html#observable_collections the ObservableList is used for accessing it's items using key integer, i.e.:

    <data>
        <import type="android.databinding.ObservableList"/>
        <import type="com.example.my.app.Fields"/>
        <variable name="user" type="ObservableList&lt;Object&gt;"/>
    </data>
    …
    <TextView
       android:text='@{user[Fields.LAST_NAME]}'
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"/>
    

    Therefore, when something is changed inside the ObservableList it triggers BindingAdapter to update UI. I think that is the main purpose of using ObservableList for now while DataBinding is in development state. Maybe in the future DataBinding will be updated with a new SomeObservableList which will be intended to use in RecyclerView. Meanwhile, you can use if (this.items == items){return;} if it works for you, or reconsider your logic of using ObservableList.