I've implemented a recyclerView with a drag and drop feature in my app. Everything works fine until the app is relaunched --any drag and drop changes were not saved/remembered by the app.
I've tried:
Using SharedPreference + GSON
Reading other SQLite answers here on SO like this one: Store new position of RecyclerView items in SQLite after being dragged and dropped
Reading Paul Burke's Medium Post
My current code looks like this:
In onCreate
ItemViewModel itemViewModel = ViewModelProviders.of(this).get(ItemViewModel.class);
itemViewModel.getAllItems().observe(this, new Observer<List<Item>>() {
#Override
public void onChanged(List<Item> items) {
adapter.setItemList(items);
itemList = items; //Global variable itemList
}
});
The method called when item is dragged/moved
private void swap(int firstPosition, int secondPosition) {
Collections.swap(itemList, firstPosition, secondPosition);
for(int i = 0; i < itemList.size(); i++) {
Item currentItem = itemList.get(i);
ItemViewModel.update(currentItem );
}
adapter.notifyItemMoved(firstPosition, secondPosition);
}
Any ideas how I can let my app save the reordered recyclerView after drag and drop? Thanks in advance.
If you wish to preserve the order of your items once you close your app or move to another activity, you will need to add a property to your entity files which will server as an order value for it. Once added, you could access your dao and run an UPDATE SQL in the Collections.swap method when reordering items and than on every load of the list, return the items ordered by that property.
Related
In my application, I want to scroll to a specific TextView in a RecyclerView if an intent specifies to scroll there. The original implementation involved looking at a listView, and looking at each item in it until I found a string matching the expected TextView string. I'm not sure how to replicate this for a RecyclerView. The closest I got to it was to look at the PreferenceGroupAdapter, but it seems to be a restricted class.
Create a method that returns the position of that TextView within your adapter. For this example, I created a very simple RecyclerView.Adapter that just holds a List<String> items and is looking for the string "target".
private int getTargetPosition(RecyclerView recycler) {
MyAdapter adapter = (MyAdapter) recycler.getAdapter();
for (int i = 0; i < adapter.items.size(); i++) {
if (adapter.items.get(i).equals("target")) {
return i;
}
}
throw new AssertionError("target is guaranteed to be in the list");
}
After that, it's as easy as calling scrollToPosition():
int position = getTargetPosition(recycler);
recycler.scrollToPosition(position);
I have a requirement, where I should download the ad item while scrolling and update the list. Since calling notifyDatasetChnaged(), resets everything, I'm calling notifyItemInserted(position). But, calling this duplicated the items in the list. I found that there are no repeated items in the list. But after calling notifyItemInserted, it duplicates the item. I'm not getting how to resolve this issue. This what I'm doing:
mNewsList.add(mPreviousAdPosition, newsItem);
mAdapter.notifyItemInserted(mPreviousAdPosition);
If I call, it works properly, there are no repeated items. But I don't want my list items to recreate. What can be the issue ?
I had the same problem for exactly the same use case, the solution is:
Implement this method in your Adapter :
#Override
public long getItemId(int position) {
//Return the stable ID for the item at position
return items.get(position).getId();
}
Call this method in the Constructor of your Adapter :
//Indicates whether each item in the data set can be represented with a unique identifier
setHasStableIds(true);
You can add the object at the end of the array with each object having a position along with it where it needs to be shown in the recycler view. Sort this array on the basis of position before calling notifyItemInserted(position). In this way only required data will be drawn.I have recenlty followed this approach and works very well with dynamic sections added in between in recycler view.
You should add the item at the end of the list.
mNewsList.add(newsItem);
and then notify like this.
mAdapter.notifyItemInserted(mNewsList.size()-1);
Create a temporary list and add items as mentioned below:
List<YourModel> mTmpList = new ArrayList<YourMdel>();
//add items (from 0 -> mPreviousAdPosition) to mTmpList;
for(int i=0; i<mPreviousAdPosition; i++) {
mTmpList.add(mNewsList.get(i));
}
//add item at mPreviousAdPosition
mTmpList.add(newsItem);
//add remaining items and set i<=mNewsList.size() because we ha
for(int i=mPreviousAdPosition; i<=mNewsList.size(); i++) {
mTmpList.add(mNewsList.get(i - 1)); //because we have added item at mPreviousAdPosition;
}
mNewsList = mTmpList;
mAdapter.notifyDataSetChanged();
You code should be written like this:
public class RecyclerViewAdapter extends RecyclerView.Adapter{
...
public void addData(int position, Item newsItem) {
mNewsList.add(position, newsItem);
notifyItemInserted(position);
}
...
}
and then you need to call the fun addData
I want to implement search functionality for my RecyclerView. On text changed i want to change the data that are displayed with this widget. Maybe this question has been asked before or is simple, but I don't know how the change the data that is to be shown...
My RecyclerView is defined as follows:
// 1. get a reference to recyclerView
mRecyclerView = (RecyclerView)findViewById(R.id.recyclerView);
// 2. set layoutManger
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
// 3. create an adapter
mAdapter = new ItemsAdapter(itemsData);
// 4. set adapter
mRecyclerView.setAdapter(mAdapter);
And the data that I am showing is something like:
ItemData itemsData[] = { new ItemData("Mary Richards"),
new ItemData("Tom Brown"),
new ItemData("Lucy London")
};
So when when I want to give the adapter another set of data, another array (with one item for example), what should I do?
If you have stable ids in your adapter, you can get pretty good results (animations) if you create a new array containing the filtered items and call
recyclerView.swapAdapter(newAdapter, false);
Using swapAdapter hints RecyclerView that it can re-use view holders. (vs in setAdapter, it has to recycle all views and re-create because it does not know that the new adapter has the same ViewHolder set with the old adapter).
A better approach would be finding which items are removed and calling notifyItemRemoved(index). Don't forget to actually remove the item. This will let RecyclerView run predictive animations. Assuming you have an Adapter that internally uses an ArrayList, implementation would look like this:
// adapter code
final List<ItemData> mItems = new ArrayList(); //contains your items
public void filterOut(String filter) {
final int size = mItems.size();
for(int i = size - 1; i>= 0; i--) {
if (mItems.get(i).test(filter) == false) {
mItems.remove(i);
notifyItemRemoved(i);
}
}
}
It would perform even better if you can batch notifyItemRemoved calls and use notifyItemRangeRemoved instead. It would look sth like: (not tested)
public void filterOut(String filter) {
final int size = mItems.size();
int batchCount = 0; // continuous # of items that are being removed
for(int i = size - 1; i>= 0; i--) {
if (mItems.get(i).test(filter) == false) {
mItems.remove(i);
batchCount ++;
} else if (batchCount != 0) { // dispatch batch
notifyItemRangeRemoved(i + 1, batchCount);
batchCount = 0;
}
}
// notify for remaining
if (batchCount != 0) { // dispatch remaining
notifyItemRangeRemoved(0, batchCount);
}
}
You need to extend this code to add items that were previously filtered out but now should be visible (e.g. user deletes the filter query) but I think this one should give the basic idea.
Keep in mind that, each notify item call affects the ones after it (which is why I'm traversing the list from end to avoid it). Traversing from end also helps ArrayList's remove method performance (less items to shift).
For example, if you were traversing the list from the beginning and remove the first two items.
You should either call
notifyItemRangeRemoved(0, 2); // 2 items starting from index 0
or if you dispatch them one by one
notifyItemRemoved(0);
notifyItemRemoved(0);//because after the previous one is removed, this item is at position 0
This is my answer - thanks to Ivan Skoric from his site: http://blog.lovelyhq.com/creating-lists-with-recyclerview-in-android/
I created an extra method inside my adapter class:
public void updateList(List<Data> data) {
mData = data;
notifyDataSetChanged();
}
Then each time your data changes, you just call this method passing in your new data and your view should change to reflect it.
Just re-initialize your adapter:
mAdapter = new ItemsAdapter(newItemsData);
or if you only need to remove add a few specific items rather than a whole list:
mAdapter.notifyItemInserted(position);
or
mAdapter.notifyItemRemoved(position);
If you want to change the complete Adapter in the recycler view. you can just simply set by recycler.setAdapter(myAdapter);
It will automatically remove the old adapter from recycler view and replace it with your new adapter.
As ygit answered, swapAdapter is interesting when you have to change the whole content.
But, in my FlexibleAdapter, you can update the items with updateDataSet. You can even configure the adapter to call notifyDataSetChanged or having synchronization animations (enabled by default). That, because notifyDataSetChanged kills all the animations, but it's good to have for big lists.
Please have a look at the description, demoApp and Wiki pages: https://github.com/davideas/FlexibleAdapter
I am working on a XMPP based chat in android.. and I am struck at a point where I need to update the position of an item in the listview to the top in case a new.message arrives.
The use case is.. I am on Contacts screen of the app and a new message comes.. so this contact should move to top of the list and get bold. This is what is similar to whatsapp as well
How can this be done. My class imolemebts activity and i have implemented custom list adapter.
So howcan I find if an item exists in the listview and secondly how to dynamically change position
First, keep in mind that a ListView is just a representation of a list of Objects. So if you want to know if an item is in the ListView, you just have to check if the corresponding Object is in your list of Objects.
Is the same when you want to change the position of one item, you have to change the position of the Object in the list.
Start by defining these objects:
private ArrayList<MyObject> lists = new ArrayList<MyObject>();
private MyCustomAdapter myAdapter;
The first time you create your ListView, just do as usually:
//fill your list with your objects
lists.add(myObject1);
lists.add(myObject2);
lists.add(myObject3);
//create and set the adapter
myAdapter = new MyCustomAdapter(..., ..., lists);
myListView.setAdapter(myAdapter);
Now you can know if your lists contains a specific object (which is the same that checking if an item is in your ListView) by simply testing that:
lists.contains(anObject);
Then, if you want to change the position of a specific item in the ListView, you have to create a new list and put the elements in the correct order. You can use something like that (not tested but it should work):
private ArrayList<MyObject> moveItemToTop(ArrayList<MyObject> lists, int positionOfItem) {
if (lists == null || positionOfItem < 0 || positionOfItem >= lists.size()) {
return lists;
}
ArrayList<MyObject> sortedList = new ArrayList<MyObject>();
//add the item to the top
sortedList.add(lists.get(positionOfItem));
for (int i=0; i<lists.size(); i++) {
if (i != positionOfItem) {
sortedList.add(lists.get(i));
}
}
return sortedList;
}
Or even this (which is way easier...).
Finally, call these two methods to update your ListView:
myAdapter = new MyCustomAdapter(..., ..., moveItemToTop(lists, itemPosition));
myListView.setAdapter(myAdapter);
This is how I resolved it
private void moveMessageToTop(MessageObject message) {
int index = 0;
for (Friends friend : mFriends) {
if (friend.getName().equalsIgnoreCase(message.getFrom().split("#")[0])) {
index = mFriends.indexOf(friend);
break;
}
}
if (index != 0) {
mFriends.add(0,new Friends(message.getFrom().split("#")[0], message
.getMessage()));
} else {
Friends frnd = mFriends.get(index);
frnd.setStatus(message.getMessage());
mFriends.add(0, frnd);
mFriends.remove(index);
}
((ListAdapter) lvFriends.getAdapter()).notifyDataSetChanged();
}
I'm trying to implement paging in a custom ListAdapter. Right now I'm just making the request for the next page when the last item in the ListView becomes visible, by checking in getView() if position is >= the size of ListAdapter.getCount().
It works fine, but I'm wondering if there's a better way (or a different way) that will only make the request once the last item in the list is actually visible to the user. Anyone know of a way?
I'm doing it almost the same way:
public static final int SCROLLING_OFFSET = 5;
// ...
private final ArrayList<T> items = new ArrayList<T>();
// ...
if (SCROLLING_OFFSET == items.size() - position) {
if (hasNextPage()) {
addNextPage();
}
}
private boolean hasNextPage() {
// basically calculates whether the last 2 pages contained the same # of items
}
private void addNextPage() {
// show spinner
// fetch next page in a background thread
// add to items
notifyDataSetChanged();
}
I think there is a better way to do it. Implementing the OnScrollListener interface. Take a look at this: Endless Scrolling ListView
Try removing the check altogether. In my experience, getView() is only called when the entry is about to come on screen.