Android data binding ObservableList behaviour issue
L

1

26

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.

Lucan answered 6/4, 2017 at 8:33 Comment(7)
If you add a single item to your ObservableList in your ViewModel, is BindingAdapter called again?Dumah
Yes, that's what happens, any remove/add/change to the ObservableList triggers the BindingAdapterLucan
I would keep my ObservableList in my adapter in that case.Dumah
That makes sense, but my question is right about that: why adding a databinding feature called ObservableList which is better not being used with actual databinding (i.e. set throught xml), but must be handled directly in the Adapter? This just doesn't make sense to me, they could have done an ObservableList in their Guava library that notifies callbacks just like the databinding one, but doesn't rebind when passed throught xml. It's like building an object with a purpose and then deliberately break the desired behaviour.Lucan
It feels like I'm missing the right way to use the ObservableList with databinding, it can't be "just pass the thing directly to your adapter“ because then it would have been much easier just not to make it rebind every time it changes.Lucan
You are totally right. In that case notifyItemChanged() or removed() or other notifyItem methods are never called. Whenever you change single item, notifyDataSetChanged() method is called. Like you said, that does not make sense. I hope someone makes it more understandable.Dumah
You'd better use DiffUtil instead of setting OnListChangedCallback interface because your BindingAdapter will be triggered every time you add/remove something into ObservableArrayList. That's why, your list always will be reassigned or populated again.Loudhailer
C
1

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.

Cooking answered 25/4, 2017 at 13:19 Comment(1)
So if anything changes in the list, then the data will be re-bound and the text will update. This kinda makes sense.Lucan

© 2022 - 2024 — McMap. All rights reserved.