ListView does not update when calling notifyDataSetChanged from a BaseAdapter - android

im am having difficulties to update a ListActivity when the underlying data changes.
I am using a custom (list) adapter (CustomListAdapter) derived vom BaseAdapter to fill a ListActivity with custom list elements (CustomListElement).
The elements in question can have their underlying data changed over time, either by user interaction or by changes in an underlying database. In order to announce said changes, the CustomListElement and CustomListAdapter objects can register DataSetObserver objects.
This is in essence done like this (unfortunately posting the entire code would be overkill):
public class CustomListElement extends DataSetObservable {
protected Object value;
public void setValue(Object newValue) {
this.value = newValue;
notifyChanged();
}
}
Hence a CustomListElement provides registerDataSetObserver by inheritance from DataSetObservable and announces changes by means of it's notifyChanged() method.
And for the CustomListAdapter:
public class CustomListAdaper extends BaseAdapter {
protected List<CustomListElement> elementList;
#Override
public void registerDataSetObserver(DataSetObserver observer) {
super.registerDataSetObserver(observer);
for (CustomListElement element : elementList)
element.registerDataSetObserver(observer);
}
}
I.e. the observers are "handed through".
Now, when invoking
setListAdapter(new CustomListAdapter(customElementList));
within a ListActivity this should register an android.widget.AbsListView.AdapterDataSetObserver within the setAdapter method of ListView (invoked from setListAdapter of ListActivity).
Upon notifying the registered DataSetObserver objects of any change the onChanged method of the AdapterDataSetObserver and therefor requestLayout of the ListView should be invoked. This should (to my understanding) refresh the ListView.
However, the ListView is not updated with the new data.
I realize it has been pointed out that notifyDataSetChanged and (maybe) notifyChanged should be run within a runOnUiThread environment, however that does not seem to fix the issue.
I also realize that similar questions came up, but not with this specific set of android classes, and with unsatisfying answers.
Am i missing anything? Any insight into why this breaks and how to fix it is greatly appreciated.

The registerDataSetObserver() part of the Adapter interface is for any external objects who might be interested to know when the data set changes. A ListView shouldn't really be interested in these methods... if it's BaseAdapter content changes, you call BaseAdapter.notifyDataSetChanged() which will tell the ListView to update itself.
In other words you only need to make the following tiny change:
public void setValue(Object newValue) {
this.value = newValue;
notifyDataSetChanged();
}
Actually, since you're changing the state of an existing item (rather than adding new ones etc) then notifyDataSetInvalidated() would be a better choice.
And of course you don't need any of that DataSetObserver stuff unless you actually do have other objects elsewhere that need to know about this data.

The issue is resolved. The problem was in fact at a different point (an intermediate class that was not mentioned here didn't react appropriately to changes). The initial code works beautifully.
Thanks alot for the effort,

adapter.notifyDataSetChanged();

Related

DiffUtil in Xamarin.Android

Junior developer here so please play nice :)
My app uses a RecyclerView to display a list of items returned from a server. The adapter and refreshing works fine however, the app hangs/freezes temporarily when updating/refreshing the list.
I'm confident that it's freezing when it hits NotifyDataSetChanged() since this redraws everything in the list (there can be hundreds of items in the list). After looking online, it appears that DiffUtil may be exactly what I'm after but I can't find any documentation or tutorials for Xamarin.Android, just regular Java based Android and I don't understand either language enough to translate it.
If anybody can point me in the right direction it would be much appreciated!
I was able to get DiffUtil working in Xamarin.Android after reading this article from VideoLAN: https://geoffreymetais.github.io/code/diffutil/. He explains it very well and the examples in his project are very useful.
Below is a "universal" version of my implementation. I would recommend reading up on what each of the override calls does before implementing your own callback (refer to link above). Believe me, it helps!
The callback:
using Android.Support.V7.Util;
using Newtonsoft.Json;
using System.Collections.Generic;
class YourCallback : DiffUtil.Callback
{
private List<YourItem> oldList;
private List<YourItem> newList;
public YourCallback(List<YourItem> oldList, List<YourItem> newList)
{
this.oldList = oldList;
this.newList = newList;
}
public override int OldListSize => oldList.Count;
public override int NewListSize => newList.Count;
public override bool AreItemsTheSame(int oldItemPosition, int newItemPosition)
{
return oldList[oldItemPosition].Id == newList[newItemPosition].Id;
}
public override bool AreContentsTheSame(int oldItemPosition, int newItemPosition)
{
// Using JsonConvert is an easy way to compare the full contents of a data model however, you can check individual components as well
return JsonConvert.SerializeObject(oldList[oldItemPosition]).Equals(JsonConvert.SerializeObject(newList[newItemPosition]));
}
}
Instead of calling NotifyDataSetChanged() do the following:
private List<YourItem> items = new List<YourItem>();
private void AddItems()
{
// Instead of adding new items straight to the main list, create a second list
List<YourItem> newItems = new List<YourItem>();
newItems.AddRange(items);
newItems.Add(newItem);
// Set detectMoves to true for smoother animations
DiffUtil.DiffResult result = DiffUtil.CalculateDiff(new YourCallback(items, newItems), true);
// Overwrite the old data
items.Clear();
items.AddRange(newItems);
// Despatch the updates to your RecyclerAdapter
result.DispatchUpdatesTo(yourRecyclerAdapter);
}
It is possible to optimise it even more by using custom payloads etc. but this is already head and shoulders above calling NotifyDataSetChanged() on your adapter.
Last few things that I spent a while trying to find online:
DiffUtil does work in fragments
DiffUtil can update an empty list (i.e. there doesn't need to be pre-existing data)
The animations are handled by the system (i.e. you don't have to add them yourself)
The method that calls DispatchUpdatesTo(yourRecyclerAdapter) does not have to be within your adapter, it can be within your activity or fragment
This is quite new for me too, and I have seen this before. I tried it literally just now, and got it working after a half an hour.
So some of it comes from here: https://medium.com/#iammert/using-diffutil-in-android-recyclerview-bdca8e4fbb00
And basically what it says is to:
Have 2 different points of your data structure (List, IEnumerable, etc...) it sounds like you already have that, so that's good.
Have a DiffUtil.Callback class where you'll be passing in the old and new data that this class will compare one against the other.
Have a method that will dispatch the updates along with your utility class. Though how the post has it is a bit wrong since he didn't update the old data. But if you did that, then it'll have to work as it does for me.
Let me know if you have questions or run into issues.

Nested RecyclerViews MVP implementation

I want to make an app that has a vertical RecyclerView with nested horizontal RecyclerViews. I don't understand how to use properly an MVP pattern in such case. MVP "rule" says that it should be only one View for a screen.
My View interface:
public interface ViewLayer {
void showProductsInCategory(int categoryId, List<ProductModel> productList, PresenterLayer presenter);
void showCategories(List<CategoryModel> categoryItemList, PresenterLayer presenter);
}
Presenter:
public interface PresenterLayer {
void onViewReady();
}
Model:
public interface InteractorLayer {
void getProducts(int categoryId);
void getCategories();
}
Model listener interface:
public interface InteractorListener {
void onProductsLoaded(int id, List<ProductModel> products);
void onCategoriesLoaded(List<CategoryModel> categories);
}
CategoryModel:
public class CategoryModel {
private String categoryName;
private List<ProductModel> productList;
public String getCategoryName() {
return categoryName;
}
public void setCategoryName(String categoryName) {
this.categoryName = categoryName;
}
public List<ProductModel> getProductList() {
return productList;
}
public void setProductList(List<ProductModel> productList) {
this.productList = productList;
}
}
So I have to select each nested RecyclerView by categoryId to add the data to their adapter. Can I create separate Model-View-Presenter interfaces for every horizontal RecyclerView?
UPD:
Step by step
1) MainActivity.onCreate calls presenter.onViewReady()
2) Presenter calls interactorLayer.getCategories()
3) Model calls InteractorListener.onCategoriesLoaded(List<CategoryModel> categories)
4) Presenter calls ViewLayer(MainActivity) showCategories(List<CategoryModel> categoryItemList, PresenterLayer presenter)
5) MainActivity sets that categoryItemList to the outer RecyclerView's adapter. Now each categoryItem has null productList
6) In the method onCategoriesLoaded(...) after ViewLayer.showCategories(...) Presenter calls Model's InteractorLayer.getProducts(i) in the cycle for each Category
7) After any productList loaded Presenter calls ViewLayer's showProductsInCategory(...)
8) MainActivity gets the Adapter of the main RecyclerView, gets a Category item and sets the productList for it.
9) MainActivity calls Adapter's notifyDataSetChanged()
10) The inner RecyclerView sets new productList when onBinding calls
I think its very complicated. What can I do with that?
UPD 03/24/2017
Source code: https://github.com/Lex74/ProductsShop
First, I'd like to state that I don't think of myself as a MVP guru, rather as someone who's striving to understand the pattern,
My favourite MVP reference: The Clean Architecture from Uncle Bob's blog
According to this blog post, there is something called The Dependency Rule:
...source code dependencies can only point inwards. Nothing in an inner circle can know anything at all about something in an outer circle...
For example, the Presenter class does not need to know anything about RecyclerView or RecyclerView.Adapter. It needs some interface to pass information to the outer layer.
The methods of the interface depend on the use case: with a List, one would like to be able to
pass a reference to the whole data List (showCategories())
refresh single list items (showProductsInCategory())
So I think the Dependency Rule says, that the ViewLayer interface has to offer methods which satisfy the needs of the [Model layer and the] Presenter layer. As a Presenter, I simply don't care whether the View out there is a ListView or maybe not a View at all but rather some combination of sound and vibration signals.
On the other hand, it seems to be perfectly ok for a View class to know the name (and methods of) its Presenter class, so maybe the PresenterLayer interface is no must-have.
It's entirely up to the View how the data will be offered to the user. A nested View structure still is just a complicated View. So I dont' think one needs to provide nested interfaces.
In some cases with nested Lists, the Presenter might need a method to update an item of the inner List, something like showSingleProductInCategory(ProductModel product, int categoryPosition, int productPosition).
Another interesting question: who keeps (and may modify) the data? In my opinion, the Presenter is responsible for the data, and it should only pass a reference to the data into the View layer or notify it of changes. An Adapter should not have the right to modify the original data list, a Presenter should never have to ask the Adapter "how many items are there?" and I don't really like the idea of two separate data lists. The names of the various notify... methods seem to indicate that I'm on the right track there.
This means Presenter will always hold on to the original data List. If data changes, the Presenter will update its data (may be clear() and "copy the new items", may also be more fine-grained, depending on what ProductLoader is offering) Afterwards, Presenter will notify the Adapter via the ViewLayer interface.
Link to a zip file with the modified Java classes
EDIT
Somehow I doubt that "one View for one screen" will work well for Android. Imagine the typical Master-Detail situation. If the screen is large, you will want to use the space and show both Fragments at once.
So if you have one View (and one Presenter) per Fragment, everything will work for all types of screens. It's up to the Activity to manage the Fragments depending on the screen size.
I've already explained that I like to have the Adapter of some ListView or RecyclerView implement the interface which is required as a callback for the Presenter. (All the Fragment in its role as callback could do would be to pass the information on to the Adapteranyway)
On the other hand, a Fragment may well contain several groups of data. Some of them may be somehow related (like all the songs by one particular artist), others (all those ads...) rather not. The Presenter needs methods to tell the View what to show to the user: one method for the artist, one for the advertisement etc.
So if I had an app with a handful of Fragments, the interface would contain methods like
void showAdvertisement(AdObject ad);
void showArtistInfo(Artist artist);
... and the Presenter would expect some class implementing this specific interface in its Constructor. (Plus the Adapter for the songs), and I'd have the Fragment implement the interface for all the non-collection data.
In a project with several apps, one might consider using generic interfaces
(one for any kind of detail information, one for collections). Then one would have a method showData(T data), and the Presenter in the example above would expect one callback for the advertisement and one for the artist info:
MyPlaylistPresenter (DetailInterface<AdObject> adCallback, DetailInterface<Artist> artistCallback, CollectionInterface<Song> songsCallback){...}
and then in the Fragment one would write:
MyPlaylistPresenter presenter = new MyPlaylistPresenter(this, this, adapter);
A little bit like Lego :), but all in all less interface classes. And methods which do basically the same thing have the same name all over the project, so I think it contributes to maintainability.
Now about your other question:
If your app has a Model on the client side, then I think you're right.
On the other hand, there are projects where the Model is part of the backend. Then the Presenter would be the logical choice.

In the MVP pattern, should adapters hold models or should the presenter hold models and have the adapter reference it?

Currently I have it so that an adapter has a reference to all the models in it. But is it better to let the presenter just hold the models and the adapter can simply reference them?
So for example:
public class Adapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{
private Presenter presenter;
public Adapter(Presenter presenter){
this. presenter = presenter;
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
Model m = presenter.getModels().get(position);
// bind model to view holder
}
#Override
public int getItemCount() {
return presenter.getModels().size();
}
}
This way when a Presenter fetches more models, it just simply calls getAdapter().notfiyDataSetChanged(); after the fetch.
You can really go either way with it. Some would say treat the adapter as part of your view and make it as dumb as possible, but there's definitely a benefit to letting the adapter hold the data if you do it right.
For example, I use an abstract base adapter with generics that holds a list of data objects to drive the recyclerview. It provides all the standard CRUD operations for the list (add, update, delete, move, etc). These methods also handle notifying the adapter of the change, so my client code doesn't have to worry about it. It just hands an object to the adapter or tells it to delete/change one, and the adapter handles the rest.
The big benefit here is a huge reduction in the amount of repeated boilerplate code for CRUD operations and dataset change notifications across the various actors interacting with recyclerviews. If you have more than a screen or two with recyclerviews, this savings adds up quick to make it more beneficial than blindly adhering to a mantra.
Normally Adapter considered to be an implementation detail of View.
Presenter should not know View implementation details.
The job of adapter is to hold an array of items and to publish it to views. Adapter should not know about Presenter, models, other views, etc.
Data flow for Adapter, as I understand it:
Model -> Presenter -> View -> Adapter-> ItemView
Control flow is opposite, preferably skipping adapter.
Feel free to ask questions in the project's issues.

Multiple listeners in one class

I guess everybody knows the listener interface pattern. I have a class with some favorite items the user can add or remove.
In this class I have:
public static OnFavsChangedListener onFavsChangedListener;
public interface OnFavsChangedListener {
abstract void onFavsChanged();
}
public static void setOnFavsChangedListener(
OnFavsChangedListener listener) {
onFavsChangedListener = listener;
}
public void addToFavorites () {
if (onFavsChangedListener != null) {
onFavsChangedListener.onFavsChanged()
}
}
This works great when I only need to use ONE of those listeners, when I use setOnFavsChangedListener from different Activities, they will overwrite the last one.
My idea was to use an ArrayList of listeners and call them all when my Favs change, but it's hard to not let the ArrayList grow too big (Activities might add a listener on every orientation change / onCreate). I could use a HashMap of IDs and listeners, let every Activity remove it's listener onDestroy, but that seems clumsy.
TL;DR: Whats an elegant way for several Activities to be informed when my Favs change?
This may be overkill for your use case, but for notifying classes about events I use an event bus (specifically https://github.com/greenrobot/EventBus).
This allows you to simply post a message and every class that has registered to receive that type of message gets it. Very simple to use, and pretty small as far as libraries go.

AdapterView<ListAdapter>.AdapterDataSetObserver is not resolvable to a type from an AdapterView<ListAdapter> subclass

I'm implementing AdapterView<ListAdapter> to produce an AbsListView-like class I can use with a CursorAdapter in a layout. I'm implementing this because I want to use the handy automatic data update behaviour CursorAdapter gives you; additionally, I can reuse the same adapter in a more conventional ListView elsewhere in my app.
I'm basing my class heavily on the Android source for AbsListView.
I'm having trouble with this though: in my own class, also extending AdapterView<ListAdapter>, I put this code:
class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
#Override
public void onChanged() {
super.onChanged();
//my update code here
}
#Override
public void onInvalidated() {
super.onInvalidated();
//my shutdown code here
}
}
Eclipse says "AdapterView.AdapterDataSetObserver cannot be resolved to a type".
I can't see that this is controlled by an import, and clearly since ListView can override this class, I would expect to be able to as well. Why isn't it visible?
The AdapterView.AdapterDataSetObserver is package private according to the javadoc. See the link here: http://www.androidjavadoc.com/1.0_r1_src/android/widget/AdapterView.html .
Thus it will not be visible outside of the package.

Categories

Resources