How to insert/remove items from RecyclerView using MVP [closed] - android

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 1 year ago.
Improve this question
While using recycler view in MVP, where do you guys keep a reference to the list? I have a ChatManager which can talk to different presenters. I keep two copies of the list of messages , one in the ChatManager and the other in the Presenter. The adapter, view, and the presenter share the same list.
The reason I have a reference to the list of messages in the Presenter is because I have some business logic for removal and scrolling which I want to handle from the presenter.
So now when I have to remove a message, the presenter decides what item to remove and removes it from the list. Now it needs to let the view know that a message has been removed. So in that case, should it say view.remove(message) or view.remove(index)? The view should not try and remove the message again since the presenter has already done that.
Same thing for other operations like scrolling or adding. If new messages are received, the presenter adds the newMessages to allMessages and then has to update the view. Ideally, the presenter should be calling view.onMessagesReceived(List<Message> messages) instead of view.onMessageReceived(int newMessagesCount, int indexAddedAt). The second method is really weird and not verbose at all. But since the list is being shared, the view only needs to call notifyItemInserted and hence only needs to know about the count and the index.
What is the best way to handle this?

So as Amir suggested, one way to go about it is expose a method from the adapter to update the data, and call notifyDataSetChanged. I really didn't want to use this method as it unnecessarily refreshes the entire data set when I only wanted to add/update messages in the adapter.
Another way was to expose a method addNewMessages(int count, int index) in the view, so that the adapter knows which indices to notify insert/update for, which I again didn't want to use because then the API seems weird as addNewMessages should actually be passed the list of new messages instead of the index and count.
The solution I went with was to actually pass the entire updated list from the Presenter to the View, which eventually passes it to the Adapter. The Adapter then uses DiffUtil to figure out the diff between the old list and the new list and calls only the notifyItemInserted/notifyItemChanged methods instead of notifyDataSetChanged every time.
This helps because now the API looks like this:
ChatPresenter {
interface View {
...
void updateMessages(List<Message> messages);
}
Activity implements ChatPresenter.View {
...
#Override
void updateMessages(List<Message> messages) {
chatsAdapter.update(messages);
}
}
ChatsAdapter {
...
public update(List<Message> messages) {
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new ChatMessageDiffCallback(this.messages, updatedMessages));
this.messages = updatedMessages;
diffResult.dispatchUpdatesTo(this);
}
}

the best practice is to update adapter list from the view implementation but the function for this task should be on view interface and implemented in view implementation (e.g. fragment or activity)
firstly add a method to your adapter like below
public void updateItemsList(ItemsList itemsList)
{
//update the list of your items assume your items list is named "mItemsList"
this.mItemsList = itemsList;
notifyDataSetChanged();
}
then in your view interface add a method to update the adapter list like below
public void updateAdapterList(ItemsList itemsList);
then in your view implementation (fragment or activity) add this:
#Override
public void updateAdapterList(ItemsList itemsList);
{
//update the adapter list -- assume your adapter is named "mAdapter"
mAdapter.updateItemsList(itemsList);
}
now you can access this method (updateAdapterList()) wherever you have an object of type view interface.

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.

What is good implementation on updating items that shift around a lot in RecyclerView (swapping), and immediate user feedback?

Goal(s):
1: Effortless updating for dynamic items.
Example:
I have a List<T> returned from an API, I use that list in my RecyclerView.Adapter. User swipes to refresh and a new list is returned from the API containing some new items and some updated old items. Now the older list needs to remove duplicate items.
Note: assume all items have an updated attribute that might change if a user interacts with it.
2: Immediate user feedback (this might tie in with goal #1).
Example:
To insert a new item into the RecyclerView.Adapter it needs to be created in an API first. Implementation creates object in the RecyclerView.Adapter and in the API simultaneously. When the new object is returned from the API the immediate object that was previously injected right away into the RecyclerView.Adapter "syncs" with the API response. This way the user sees immediate feedback.
Code Example:
I don't really have anything in mind for Goal #1 BUT for Goal 2 I was thinking something like this maybe inside my ViewHolder? (I have heard that updating / syncing models in Viewholders is not a good practice in general because viewholders recycle):
// JAVA 7
private void createNewObjectToBeInsertedIntoRecyclerView(String data) {
// Pass callback to API and at the same time insert object into adapter
mAdapter.addNewObject(data);
mPresenter.createObject(new SyncRequestCallback() {
#Override
public void onSuccessFromAPI(ModelObject model) {
mAdapter.updateObject(model);
}
});
}
// JAVA 8
private void createNewObjectToBeInsertedIntoRecyclerView(String data) {
// Pass callback to API and at the same time insert object into adapter
mAdapter.addNewObject(data);
mPresenter.createObject((sync) -> { mAdapter.updateObject(model); });
}
This is just off the top of my head and it is definitely bug prone.
How Can I Achieve This?:
Looking for a robust solution here, but something that doesn't involve content providers (if possible).
You should not do anything like that in the ViewHolder, just bind the data you got from the API to the UI.
What you should do is operate on the Adapter
when the new List<T> returns from the API, just make the old list in the adapter to point to this new one (oldList = newList) and call mAdapter.notifyDataSetChanged()
You can do like point 1) but that way updates the whole Adapter. If you know where in the Adapter you have inserted that item (and I assume you know), just call mAdapter.notifyItemInserted(position) or alternatively, if you have already created it the Adapter, call mAdapter.notifyItemChanged(position)

ListView does not update when calling notifyDataSetChanged from a BaseAdapter

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();

Categories

Resources