I want simple example for MVP structure in android to refresh recyclerview item not the whole list of recyclerview.
It will refresh only items in recyclerview of android.
This is a problem I've thought about quite a lot. There are two possible ways of doing it:
Pass the new List of data to the Adapter and it does the work of working out what's changed and updating the correct items.
Keep a record of the current items in your model then when the new list is calculated send ListChangeItems to the Adapter.
I'll outline both in more detail below. In both cases you need to calculate the differences between what is currently showing and the new data. I have a helper class ListDiffHelper<T> which does this comparison:
public class ListDiffHelper<T> {
private List<T> oldList;
private List<T> newList;
private List<Integer> inserted = new ArrayList<>();
private List<Integer> removed = new ArrayList<>();
public List<Integer> getInserted() {return inserted;}
public List<Integer> getRemoved() {return removed;}
public ListDiffHelper(List<T> oldList, List<T> newList) {
this.oldList = oldList;
this.newList = newList;
checkForNull();
findInserted();
findRemoved();
}
private void checkForNull() {
if (oldList == null) oldList = Collections.emptyList();
if (newList == null) newList = Collections.emptyList();
}
private void findInserted() {
Set<T> newSet = new HashSet<>(newList);
newSet.removeAll(new HashSet<>(oldList));
for (T item : newSet) {
inserted.add(newList.indexOf(item));
}
Collections.sort(inserted, new Comparator<Integer>() {
#Override
public int compare(Integer lhs, Integer rhs) {
return lhs - rhs;
}
});
}
private void findRemoved() {
Set<T> oldSet = new HashSet<>(oldList);
oldSet.removeAll(new HashSet<>(newList));
for (T item : oldSet) {
removed.add(oldList.indexOf(item));
}
Collections.sort(inserted, new Comparator<Integer>() {
#Override
public int compare(Integer lhs, Integer rhs) {
return rhs - lhs;
}
});
}
}
For this to work properly you need to ensure that the equals() method of the Data class compares things in a suitable way.
Adapter Lead
In this case your Presenter calls getData() on the model (or subscribes to it if you're using Rx) and receives List<Data>. It then passes this List to the view through a setData(data) method which in turn give the list to the Adapter. The method in the Adapter would look something like:
private void setData(List<Data> data) {
if (this.data == null || this.data.isEmpty() || data.isEmpty()) {
this.data = data;
adapter.notifyDataSetChanged();
return;
}
ListDiffHelper<Data> diff = new ListDiffHelper<>(this.data, data);
this.data = data;
for (Integer index : diff.getRemoved()) {
notifyItemRemoved(index);
}
for (Integer index : diff.getInserted()) {
notifyItemInserted(index);
}
}
It is important to remove items first before adding new ones otherwise the order will not be maintained correctly.
Model Lead
The alternative approach is to keep the Adapter much dumber and do the calculation of what has changed in your model layer. You then need a wrapper class to send the individual changes to your View/Adapter. Something like:
public class ListChangeItem {
private static final int INSERTED = 0;
private static final int REMOVED = 1;
private int type;
private int position;
private Data data;
public ListChangeItem(int type, int position, Data data) {
this.type = type;
this.position = position;
this.data = data;
}
public int getType() {return type;}
public int getPosition() {return position;}
public Data getData() {return data;}
}
You would then pass a List of these to your Adapter via the view interface. Again it would be important to have the removals actioned before the inserts to ensure the data is in the correct order.
Related
I have a RecyclerView that shows a list of CardViews. I recently switched the project from using RecyclerView Adapter to using an AsyncListDiffer Adapter to take advantage of adapter updates on a background thread. I have converted over all previous CRUD and filter methods for the list but cannot get the sort method working.
I have different types or categories of CardViews and I would like to sort by the types/categories. I clone the existing list mCards so the "behind the scenes" DiffUtil will see it as a different list, as compared to the existing list that I wanted to sort. And then I use AsynListDiffer's submitList().
The list is not sorting. What am I missing here?
MainActivity:
private static List<Card> mCards = null;
...
mCardViewModel = new ViewModelProvider(this).get(CardViewModel.class);
mCardViewModel.getAllCards().observe(this,(cards -> {
mCards = cards;
cardsAdapter.submitList(mCards);
}));
mRecyclerView.setAdapter(cardsAdapter);
A click on a "Sort" TextView runs the following code:
ArrayList<Card> sortItems = new ArrayList<>();
for (Card card : mCards) {
sortItems.add(card.clone());
}
Collections.sort(sortItems, new Comparator<Card>() {
#Override
public int compare(Card cardFirst, Card cardSecond) {
return cardFirst.getType().compareTo(cardSecond.getType());
}
});
cardsAdapter.submitList(sortItems);
// mRecyclerView.setAdapter(cardsAdapter); // Adding this did not help
AsyncListDifferAdapter:
public AsyncListDifferAdapter(Context context) {
this.mListItems = new AsyncListDiffer<>(this, DIFF_CALLBACK);
this.mContext = context;
this.mInflater = LayoutInflater.from(mContext);
}
public void submitList(List<Quickcard> list) {
if (list != null) {
mListItems.submitList(list);
}
}
public static final DiffUtil.ItemCallback<Card> DIFF_CALLBACK
= new DiffUtil.ItemCallback<Card>() {
#Override
public boolean areItemsTheSame(#NonNull Card oldItem, #NonNull Card newItem) {
// User properties may have changed if reloaded from the DB, but ID is fixed
return oldItem.getId() == newItem.getId();
}
#Override
public boolean areContentsTheSame(#NonNull Card oldItem, #NonNull Card newItem) {
return oldItem.equals(newItem);
}
#Nullable
#Override
public Object getChangePayload(#NonNull Card oldItem, #NonNull Card newItem) {
return super.getChangePayload(oldItem, newItem);
}
};
Model:
#Entity(tableName = "cards")
public class Card implements Parcelable, Cloneable {
// Parcelable code not shown for brevity
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "cardId")
public int id;
#ColumnInfo(name = "cardType")
private String type;
#Ignore
public Card(int id, String type) {
this.id = id;
this.type = type;
}
public int getId() {
return this.id;
}
public String getType() {
return this.type;
}
#Override
public boolean equals(Object obj) {
if (obj == this)
return true;
else if (obj instanceof Card) {
Card card = (Card) obj;
return id == card.getId() &&
type.equals(card.getType());
} else {
return false;
}
}
#NonNull
#Override
public Card clone() {
Card clone;
try {
clone = (Card) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
return clone;
}
Instead of using notifyDataSetChanged() we can use notifyItemMoved(). That solution gives us a nice animation of sorting. I put the sort order within the adapter. We need a displayOrderList that will contain the currently displayed elements because mDiffer.getCurrentList() doesn't change the order of elements after notifyItemMoved(). We first moved the element that is first sorted to the first place, the second sorted element to second place,... So inside the adapter put the following:
public void sortByType()
{
List<Card> sortedList = new ArrayList<>(mDiffer.getCurrentList());
sortedList.sort(Comparator.comparing(Card::getType));
List<Card> displayOrderList = new ArrayList<>(mDiffer.getCurrentList());
for (int i = 0; i < sortedList.size(); ++i)
{
int toPos = sortedList.indexOf(displayOrderList.get(i));
notifyItemMoved(i, toPos);
listMoveTo(displayOrderList, i, toPos);
}
}
private void listMoveTo(List<Card> list, int fromPos, int toPos)
{
Card fromValue = list.get(fromPos);
int delta = fromPos < toPos ? 1 : -1;
for (int i = fromPos; i != toPos; i += delta) {
list.set(i, list.get(i + delta));
}
list.set(toPos, fromValue);
}
and then call from activity cardsAdapter.sortByType();
I think issue is in below method
public void submitList(List<Quickcard> list) {
if (list != null) {
mListItems.submitList(list);
}
}
because here first you need to clear old arraylist "mListItems" using
mListItems.clear();
//then add new data
if (list != null) {
mListItems.addAll(list);
}
//now notify adapter
notifyDataSetChanged();
Or Also you can direct notify adapter after sorting.
First set adapter and pass your main list in adapter's constructor
Collections.sort(sortItems, new Comparator<Card>() {
#Override
public int compare(Card cardFirst, Card cardSecond) {
return cardFirst.getType().compareTo(cardSecond.getType());
}
});
//now direct notify adpter
your_adapter_object.notifyDataSetChanged();
I clone the existing list mCards so the "behind the scenes" DiffUtil will see it as a different list
DiffUtil will detect changes by your implementation of DiffUtil.ItemCallback<Card>, so when you clone a card you just create a new instance of it with the same ID, therefor DiffUtil sees it as the same item because you are checking if the items are the same based on their ID:
#Override
public boolean areItemsTheSame(#NonNull Card oldItem, #NonNull Card newItem) {
// User properties may have changed if reloaded from the DB, but ID is fixed
return oldItem.getId() == newItem.getId();
}
but because the cloned object's reference is different from the original item, DiffUitl will detect the items content change, because of:
#Override
public boolean areContentsTheSame(#NonNull Card oldItem, #NonNull Card newItem) {
return oldItem.equals(newItem);
}
so DiffUtil will call adapter.notifyItemChanged(updateIndex) and will update the viewHolder in that index, but it won't change the order of items in the recyclerView.
There seems to be another problem in your sort code. You say
I would like to sort by the types/categories
but you are sorting your list alphabetically every time you click the textView. This code will give you same result every time you run it and won't change the order of recyclerView assuming the original list is sorted in the first hand. If the original list isn't sorted, clicking the textView will change the order just once.
When I press the toggle button, I want to change the units of the list of the currently displayed recycler views at once.
I used ListAdapter + DiffUtil to display the recycler view.
The way I tried to implement this feature is to load the current list when the toggle button is pressed.
Then, after resetting the new toggle unit values for the current lists, I used submitList() to update the list.
But this was the wrong way.
My guess is because the variable created for the value of the list loaded to be updated has the same reference value, so the value changed at the same time.
In other words, there is no change because the values of the update list and the existing list are the same.
What can I do to solve this problem?
RoutineDetailModel.java
public class RoutineDetailModel {
public int id;
private int set = 1;
public static String unit = "kg";
public RoutineDetailModel() {
Random random = new Random();
this.id = random.nextInt();
}
public RoutineDetailModel(int set) {
Random random = new Random();
this.id = random.nextInt();
this.set = set+1;
}
public int getSet() {
return set;
}
public int getId() {
return id;
}
public String getWeight() {
return weight;
}
public String getUnit() {
return unit;
}
#Override
public int hashCode() {
return Objects.hash(set, weight); // getWeight를 호출하면 더 다양하게 되나?
}
#Override
public boolean equals(#Nullable Object obj) {
if(obj != null && obj instanceof RoutineDetailModel) {
RoutineDetailModel model = (RoutineDetailModel) obj;
if(this.id == model.getId()) {
return true;
}
}
return false;
}
}
MainActivity.java
public class WriteRoutineActivity extends AppCompatActivity implements WritingCommentDialogFragment.OnDialogClosedListener {
List<RoutineModel> items;
RoutineListAdapter listAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_write_routine);
listAdapter.setOnRoutineClickListener(new RoutineListAdapter.OnRoutineItemClickListener() {
#Override
public void onUnitBtnClicked(int curRoutinePos, String unit) {
Object obj = listAdapter.getRoutineItem(curRoutinePos);
RoutineModel item = (RoutineModel) obj;
if(obj instanceof RoutineModel) {
for(RoutineDetailModel detailItem : item.getDetailItemList()) {
detailItem.setUnit(unit);
}
listAdapter.submitList(getUpdatedList());
}
}
});
}
}
Please tell me if you need more information
To change all the items you need to use notifyDataSetChanged() otherwise you cannot update ALL the items.
In order to do so, you must create a method inside your adapter which does the following ->
public void updateItems(String unit) {
for({YOUR ITEM TYPE} item: {YOUR LIST}) {
item.unit = unit;
}
notifyDataSetChanged();
}
And call this method when you want to change all units.
yourAdapter.updateItems("Kg");
i am trying to sort my Arrayadapter with it's sort-Method
#Override
public void sort(#NonNull Comparator<? super Task> comparator) {
super.sort(comparator);
}
two times.
I'm using the two Comparators
private static final Comparator TASKCOMPARATOR_TITLE = new Comparator<Task>() {
#Override
public int compare(Task a, Task b) {
return a.getTitle().compareToIgnoreCase(b.getTitle());
}
};
private static final Comparator TASKHOLDERCOMPARATOR_DUEDATE = new Comparator<ViewHolder>() {
#Override
public int compare(ViewHolder a, ViewHolder b) {
return a.getTask().getDueTime().compareTo(b.getTask().getDueTime());
}
};
like this
taskAdapter.sort(Util.getTASKCOMPARATOR_TITLE());
taskAdapter.sort(Util.getTASKCOMPARATOR_DUEDATE());
hoping to secondary sort the ArrayAdapter by the title and then by the Date. The sort-Method of the ArrayAdapter is internally using
public void sort(Comparator<? super T> comparator) {
synchronized (mLock) {
if (mOriginalValues != null) {
Collections.sort(mOriginalValues, comparator);
} else {
Collections.sort(mObjects, comparator);
}
}
if (mNotifyOnChange) notifyDataSetChanged();
}
I've read, that Collections.sort() is using a stable algorithm and therefore i'm wondering why the List of my ArrayAdapter is just being sorted by the last comparator i call.
Can anyone tell me where i made a mistake and why the first sort()-call is being ignored by the second one?
EDIT
private static final Comparator TASKCOMPARATOR = new Comparator<Task>() {
#Override
public int compare(Task a, Task b) {
int timeCompareResult = a.getDueTime().compareTo(b.getDueTime());
if (timeCompareResult == 0) {
return a.getTitle().compareToIgnoreCase(b.getTitle());
} else {
return timeCompareResult;
}
}
};
This works. I don't know if it's the only/best way.
As Шах stated, use one comparator.
Comparator returns 0 on equals.
First compare on Title, if they are equal you go for the comparison on Time.
I haven't ran the code, but it should be something like this:
private static final Comparator TASKHOLDERCOMPARATOR_DUEDATE = new Comparator<ViewHolder>() {
#Override
public int compare(ViewHolder a, ViewHolder b) {
int comparison = a.getTask().getTitle().compareToIgnoreCase(b.getTask().getTitle());
if(comparison == 0){
comparison = a.getTask().getDueTime().compareTo(b.getTask().getDueTime());
}
return comparison;
}
};
newbie to Android here!
I've been learning how to implement SQLite in my app, and to sum it up, I have an Accountant class which has access to the SQLite database. The class pulls up the items from the database and puts them in an ArrayList. This ArrayList is what is used for my adapter for the recyclerView.
Whenever I add a new item in the app, the the item's data is stored in the database and the Accountant class's ArrayListgets updated with this info.
Then, the adapter calls its notifyDataSetChanged() method to update the View. This is where the problem occurs; the RecyclerView DOES display all items, but only upon app startup, NOT when a new item is added.
I've done all I can, it just LOOKS like it's supposed to work, but it doesn't and it's driving me nuts.
Here's the code
ItemAdapter Class
private class ItemAdapter extends RecyclerView.Adapter<ItemHolder> {
private List<Item> mItemList;
public ItemAdapter(List<Item> itemList) {
mItemList = itemList;
}
public ItemHolder onCreateViewHolder(ViewGroup parent, int ViewType) {
View view = getLayoutInflater().inflate(R.layout.list_item_item, parent, false);
return new ItemHolder(view);
}
public void onBindViewHolder(ItemHolder holder, int position) {
Item item = mItemList.get(position);
holder.bindItem(item);
}
public int getItemCount() {
return mItemList.size();
}
}
Accountant Class
public class Accountant {
private static Accountant sAccountant;
private double mTotalMoney;
private Context mContext;
private SQLiteDatabase mDatabase;
private List<Item> mItemList;
public static Accountant get(Context context) {
sAccountant = sAccountant == null ? new Accountant(context) : sAccountant;
return sAccountant;
}
private Accountant(Context context) {
mTotalMoney = 0;
mContext = context.getApplicationContext();
mDatabase = new ItemBaseHelper(mContext).getWritableDatabase();
mItemList = getListFromSQL();
}
private static ContentValues getContentValues(Item i) {
ContentValues values = new ContentValues();
values.put(ItemTable.cols.NAME, i.getName());
values.put(ItemTable.cols.PRICE, i.getPrice());
values.put(ItemTable.cols.COUNT, i.getCount());
return values;
}
public void addItem(Item item) {
ContentValues cv = getContentValues(item);
mDatabase.insert(ItemTable.NAME, null, cv);
mItemList = getListFromSQL();
}
public void removeItem(int i) {
}
public void addMoney(double money, boolean isSet) {
mTotalMoney += isSet ? money - mTotalMoney : money;
}
public String getTotalMoney() {
return MoneyUtils.prep(mTotalMoney);
}
public String getChange() {
double cost = 0;
for (Item item : getItemList())
cost += item.getPrice() * item.getCount();
return MoneyUtils.prep(mTotalMoney - cost);
}
public List<Item> getItemList() {
return mItemList;
}
private List<Item> getListFromSQL() {
List<Item> itemList = new ArrayList<>();
ItemCursorWrapper cursor = queryItems(null, null);
try {
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
itemList.add(cursor.getItem());
cursor.moveToNext();
}
} finally {
cursor.close();
}
return itemList;
}
public ItemCursorWrapper queryItems(String whereClause, String[] whereArgs) {
Cursor cursor = mDatabase.query(ItemTable.NAME, null, whereClause, whereArgs, null, null, null);
return new ItemCursorWrapper(cursor);
}
public String individualPriceOf(Item i) {
return MoneyUtils.prep(i.getPrice());
}
public String totalPriceOf(Item i) {
return MoneyUtils.prep(i.getCount() * i.getPrice());
}
public String countOf(Item i) {
return String.valueOf(i.getCount());
}
public void clearList() {
mDatabase.delete(ItemTable.NAME, null, null);
}
}
Item adding logic
public void addItem(Item item) {
mAccountant.addItem(item);
mAdapter.notifyItemInserted(mAccountant.getListFromSQL().size() - 1);
mAdapter.notifyDataSetChanged();
mChangeButton.setText(mAccountant.getChange());
}
Well there is fundamental problem not even related to RecyclerView.
First let's see how to fix your issue then explanation of what's wrong.
change this
private List<Item> mItemList;
to this
private final List<Item> mItemList;
then instead of any assignment like mItemList = getListFromSQL(); write this
mItemList.clear();
mItemList.addAll(getListFromSQL());
Now explanation why your code is not working. The thing is that when you assign your dataSource (i.e. mItemList) to some new value you are changing reference to it (that's a java fundamental thing) so that your RecyclerView doesn't know anything about it and it's own dataSource which you assign only once in constructor remains the same old one which is not changed therefore your notifyDataSetChanged call does nothing.
General advice whenever using RecyclerView or a ListView make sure you define your dataSource as final.
This is happening because you do not add the item into your Adpater's list. Make a method inside your adapter and call this method from your Accountant class.
private class ItemAdapter extends RecyclerView.Adapter<ItemHolder> {
public void addItem(Item item) {
mItemList.add(item); ///Add the item to your arrayList and then notify
notifyItemInserted(mItemList.size());
}
When you add single item in Adapter dont call notifyDataSetChanged() method because it will notify the whole list. Instead only use notifyItemInserted() method.
Another think is make sure when you notify the adapter it must be from UI thread.
When you add your item then just call this adapter addItem() method from your Accountant class.
public void addItem(Item item) { ///This method is from Accountant Class
mAccountant.addItem(item);
mAdapter.addItem(item); // Call the addItem() from Adapter class
mChangeButton.setText(mAccountant.getChange());
}
Hope it will work...
I write a program that parse json and shows it in a listView.
Now i want to sort it but don't know how and fail. In program i have a List<PoolOfflineModal> alist = new ArrayList<>();
so modal are this;
public class PoolOfflineModal {
private int id;
private String name;
private int price;
private int priceWithoutDiscount;
private String tel;
private int discountPercent;
private String address;
private String photo;
}
and data simple is here http://hayat.cloudsite.ir/pools/pools.json
so i want to sort List ascending with price, so update ListView and i don't know...
another idea?! or solution?!
another way are exist to sort ListView from a column without sort list?!?
this is my last Comparator
public class MyComparator implements Comparator<PoolOfflineModal> {
#Override
public int compare(PoolOfflineModal lo, PoolOfflineModal ro) {
int result = 0;
if (way == "name") {
result = lo.getName().compareTo(ro.getName());
} else if (way == "price") {
result = lo.getPrice().compareTo(ro.getPrice());
} else if (way == "percent") {
result = lo.getDiscountPercent().compareTo(ro.getDiscountPercent());
}
return result;
}
}
List Object Like This:
List<PoolOfflineModal> alist = new ArrayList<>();
Comparator Class Like this:
public class MyComparator implements Comparator<PoolOfflineModal> {
#Override
public int compare(PoolOfflineModal o1, PoolOfflineModal o2) {
return o1.id.compareTo(o2.id);
}
}
Use Like this:
Collections.sort(alist, new MyComparator());
Hope it will helpful to you.