Overview: I'm having a chat application. Till now, I was using CursorAdapter with a Listview to load my chat items in the list. But now, I'm planning to refactor the code to use RecyclerView with RecyclerView.Adapter and a "Load More" functionality like whatsapp.
Issue: Memory consumption. With CursorAdapter, items not in viewable area were getting Garbage Collected, but now since I'm using an ArrayList of my CustomModal, once you load all the items in the list (by clicking on the "Load More" button) I'm seeing high memory consumption in the memory logs (No Garbage Collection).
My guess is now, I'm loading all the items in an ArrayList and that is causing the issue. Is that it?
Is there a way to avoid the issue or optimize the problem?
EDIT:
Can't post the complete code here, but here is a snippet of the kind of Adapter that I've implemented:
public class MessageAdapter extends RecyclerView.Adapter<MessageAdapter.MyViewHolder> {
private ArrayList<MyModal> mMyModals;
public MessageAdapter(ArrayList<MyModal> mMyModals) {
this.mMyModals = mMyModals;
//... Some fields initialization here
}
public void changeList(ArrayList<MyModal> myModals, boolean isLoadMoreEnabled){
this.mMyModals = myModals;
//... Some fields initialization here
notifyDataSetChanged();
}
public void toggleLoadMore(boolean isLoadMoreEnabled){
if(isLoadMoreEnabled){
//..Checks if load more is already enabled or not
//..If not then enables it by adding an item at 0th poition of MyModal list
//..Then notifyDataSetChanged()
}else{
//..Checks if load more is already disabled or not
//..If not then disables it by removing an item at 0th poition of MyModal list
//..Then notifyDataSetChanged()
}
}
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
MyViewHolder messageViewHolder = null;
View itemLayoutView = null;
MyModal.MessageType messageType = MyModal.MessageType.getMessageTypeFromValue(viewType);
switch (messageType){
case MESSAGE_TYPE1:
itemLayoutView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.layout1, null);
messageViewHolder = new Type1ViewHolder(itemLayoutView);
break;
case MESSAGE_TYPE2:
itemLayoutView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.layout2, null);
messageViewHolder = new Type2ViewHolder(itemLayoutView);
break;
}
return messageViewHolder;
}
#Override
public void onBindViewHolder(MyViewHolder holder, int position) {
final MyModal myModal = mMyModals.get(position);
MyModal.MessageType messageType = myModal.getMessageType();
holder.initialize(myModal);
}
#Override
public int getItemCount() {
return (mMyModals != null)?mMyModals.size():0;
}
#Override
public int getItemViewType(int position) {
return mMyModals.get(position).getMessageType().getValue();
}
public abstract class MyViewHolder extends RecyclerView.ViewHolder {
public MyViewHolder(View itemLayoutView) {
super(itemLayoutView);
}
public abstract void initialize(MyModal myModal);
}
class Type1ViewHolder extends MyViewHolder {
//...Variables
public Type1ViewHolder(View itemLayoutView) {
super(itemLayoutView);
//...variables initialization here
}
#Override
public void initialize(MyModal myModal) {
//...Setting values in view using myModal
}
}
class Type2ViewHolder extends MyViewHolder {
//...Variables
public TextViewHolder(View itemLayoutView) {
super(itemLayoutView);
//...variables initialization here
}
#Override
public void initialize(MyModal myModal) {
//...Setting values in view using myModal
}
}
}
First of all :
public void changeList(ArrayList<MyModal> myModals, boolean isLoadMoreEnabled){
this.mMyModals = myModals;
//... Some fields initialization here
notifyDataSetChanged();
}
Here you are creating a new arraylist and assigning it to your mMyModals. This means there are 2 arraylists at this point and they take up twice the amount of space than required. GC doesnt work the way you expect it to. Since the arraylist is initialized in your activity it will persist as long as the arraylist persists and so will the initial arraylist.
Instead of creating a new arraylist in your activity and passing it to changeList. Just clear your old arraylist and pass that.And also in adapter changeList method you can do the below
public void changeList(ArrayList<MyModal> myModals, boolean isLoadMoreEnabled){
this.mMyModals.clear();
this.mMyModels.addAll(myModels);
//... Some fields initialization here
notifyDataSetChanged();
}
Please let me know if i am not clear. Also show your activity code if this does not work.
Instead of replacing the whole ArrayList and calling notifyDataSetChanged, try adding the items to the ArrayList and then call notifyItemRangeInserted(int positionStart, int itemCount), maybe that could work. Also, you dont have to replace the Adapter's ArrayList. Your Activity/Fragment probably has the same ArrayList, just editing this list in your Activity/Fragment and then calling notifyItemRangeInserted(int positionStart, int itemCount) should do the trick. Also, instead of retrieving all the messages, you could also try to only get the next X amount of messages, so you wont retrieve the messages you already retrieved before (if you didn't do that already).
Related
I've a strange problem with my recyclerView adapter, I just want to show/hide ImageView depending of the selection but when I call the notifyItemChanged(selection) in my click listener, it call onCreateViewHolder and take a delay to refresh de view, I don't know why and I didn't find another solution to perform what I need.
This is my adapter:
public class ChannelAdapter extends RecyclerView.Adapter<ChannelAdapter.ChannelHolder> {
private ArrayList<Integer> channelList;
private Integer selection = 0;
public ChannelAdapter(ArrayList<Integer> channelList) {
this.channelList = channelList;
}
#NotNull
#Override
public ChannelHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Log.d(TAG, "onCreateViewHolder: ");
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.adapter_channel_item_selected, parent, false);
ChannelHolder holder = new ChannelHolder(view);
return holder;
}
#SuppressLint("SetTextI18n")
#Override
public void onBindViewHolder(ChannelHolder holder, int position) {
Log.d(TAG, "onBindViewHolder: "+position);
holder.textViewChannelId.setText("#"+channelList.get(position));
holder.textViewChannel.setText(channelList.get(position).toString());
if(position==selection){
holder.imageViewSelectorLeft.setVisibility(View.VISIBLE);
holder.imageViewSelectorRight.setVisibility(View.VISIBLE);
}
else{
holder.imageViewSelectorLeft.setVisibility(View.INVISIBLE);
holder.imageViewSelectorRight.setVisibility(View.INVISIBLE);
}
}
#Override
public int getItemCount() {
return channelList.size();
}
public class ChannelHolder extends RecyclerView.ViewHolder {
TextView textViewChannel, textViewChannelId;
ImageView imageViewSelectorLeft, imageViewSelectorRight;
public ChannelHolder(View itemView) {
super(itemView);
textViewChannelId = itemView.findViewById(R.id.textViewChannelId);
textViewChannel = itemView.findViewById(R.id.textViewChannel);
imageViewSelectorLeft = itemView.findViewById(R.id.imageViewSelectorLeft);
imageViewSelectorRight = itemView.findViewById(R.id.imageViewSelectorRight);
itemView.setOnClickListener(v -> {
notifyItemChanged(selection);
selection=getAdapterPosition();
notifyItemChanged(selection);
});
}
}
}
Do I miss something or am I doing it by the wrong way?
Thanks in advance for any help
Edit :
I tried to use notifyItemChanged with a payload set to 1 and override onBindViewHolder to get the payload but it still call onCreateViewHolder, even when mSupportsChangeAnimations is set to false
By default, your RecyclerView will have a DefaultItemAnimator attached to it. When you call notifyItemChanged() on your adapter, the system will eventually call through to the DefaultItemAnimator to find out whether it needs to create a new ViewHolder or if it can "re-use" the existing one.
#Override
public boolean canReuseUpdatedViewHolder(#NonNull ViewHolder viewHolder,
#NonNull List<Object> payloads) {
return !payloads.isEmpty() || super.canReuseUpdatedViewHolder(viewHolder, payloads);
}
The superclass implementation:
#Override
public boolean canReuseUpdatedViewHolder(#NonNull RecyclerView.ViewHolder viewHolder) {
return !mSupportsChangeAnimations || viewHolder.isInvalid();
}
These suggest that there are two easy ways to make sure that the ViewHolder is reused instead of recreated:
Make sure that the payloads list is not empty. This is done by calling adapter.notifyItemChanged(position, payload). It doesn't matter what the payload is, as long as it is non-null.
Set mSupportsChangeAnimations to false for your DefaultItemAnimator.
DefaultItemAnimator animator = (DefaultItemAnimator) recyclerView.getItemAnimator();
animator.setSupportsChangeAnimations(false);
You can call your adapter like this
YourAdapter adapter = new YourAdapter(yourList, yourActivity.this);
recyclerView.setAdapter(adapter);
adapter.notifyDataSetChanged();
and then you can put this code on itemClick in adapter
if(position==getAdapterPosition()){
holder.imageViewSelectorLeft.setVisibility(View.VISIBLE);
holder.imageViewSelectorRight.setVisibility(View.VISIBLE);
}
else{
holder.imageViewSelectorLeft.setVisibility(View.INVISIBLE);
holder.imageViewSelectorRight.setVisibility(View.INVISIBLE);
}
Basically we need to get the email from the recyclerview
We tried adding onClickListener on the TextView in the RecyclerView, but when there are more than one entries in the RecyclerView, we cannot get the value.
Is there any way that I can store the values in variables before populating the RecyclerView and passing the values to another Activity?
I tried getting the values from the TextView in the RecyclerView, but I always show the text on the first row
Intent doesn't work on the Adapter class because its not an activity
ArrayList<ModelClass> objModelClassArrayList;
public DatabaseRecyclerAdapter(ArrayList<ModelClass> objModelClassArrayList) {
this.objModelClassArrayList = objModelClassArrayList;
}
#NonNull
#Override
public DatabaseViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType)
{
View singleRow= LayoutInflater.from(parent.getContext()).inflate(R.layout.single_row,parent,false);
return new DatabaseViewHolder(singleRow);
}
#Override
public void onBindViewHolder(#NonNull DatabaseViewHolder holder, int position)
{
ModelClass objModelClass=objModelClassArrayList.get(position);
holder.userNameTV.setText(objModelClass.getName());
holder.userLocation.setText(objModelClass.getAddress());
String e1=objModelClass.getEmail();
holder.userEmail.setText(e1);
}
#Override
public int getItemCount() {
return objModelClassArrayList.size();
}
public static class DatabaseViewHolder extends RecyclerView.ViewHolder
{
TextView userNameTV,userLocation,userEmail;
public DatabaseViewHolder(#NonNull View itemView)
{
super(itemView);
userNameTV=itemView.findViewById(R.id.sr_userNameTV);
userLocation=itemView.findViewById(R.id.sr_location);
userEmail=itemView.findViewById(R.id.sr_email);
}
}
I want to extract the email from a single row and pass it to another page where I can pass it in the DB query to get the results from the database.
first, need to pass context in the constructor of the adapter which can help you to open another activity from the recyclerView adapter. Then Inside BindViewHolder, you can create clickListener like as below,
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
String emailText=holder.userEmail.getText().trim();
Intent intent=new Intent(context,activity_name_you _want_ to_open);
intent.setExtra("emailName",emailText);
(name of activity contains RV)context.startActivity(intent)
//Note: here context needs to be typecasted to activity from which we want to open new activity.
}
});
So I have this app in which I have to make a RecyclerView which contains a list of items that can be deleted/edited e.t.c.
I followed a tutorial on youtube and I made a custom CardView item on another layout and a custom adapter for that item.
Thing is, depending on the state of the item, I have to change something(ex. background color or text color).
When I do that in the RecyclerView activity I get NullPointerException even if I have the id's.
How can I edit those TextViews inside the programmatically generated list of items in the moment I make the Retrofit call?
boolean isEnded;
if(!endedAt.equals("null"))
{
endedAt = endedAt.substring(endedAt.indexOf("T") + 1, endedAt.lastIndexOf(":"));
isEnded=true;
}
else
{
endedAt="ongoing";
isEnded=false;
}
item.timeDifference=createdAt+" - "+endedAt;
if(externalSystem.equals("null"))
{
item.externalSystem="";
}
else
{
item.externalSystem = externalSystem;
}
Log.i("attr",externalSystem);
items.add(item);
itemAdapter=new ItemAdapter(getApplicationContext(), items);
recyclerView.setAdapter(itemAdapter);
if(isEnded) {
error-> externalSystemView.setTextColor(Color.BLACK);
}
The app is rather big, but I think you can get the idea from this piece of code.
Here is the error: Attempt to invoke virtual method 'void android.widget.TextView.setTextColor(int)' on a null object reference
public class ItemAdapter extends
RecyclerView.Adapter<ItemAdapter.ItemViewHolder>{
private Context context;
private ArrayList<Item> itemList;
public ItemAdapter(Context context, ArrayList<Item> itemList)
{
this.context=context;
this.itemList=itemList;
}
#Override
public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater layoutInflater=LayoutInflater.from(parent.getContext());
View view=layoutInflater.inflate(R.layout.item_layout,parent,false);
ItemViewHolder itemViewHolder=new ItemViewHolder(view);
return itemViewHolder;
}
#Override
public void onBindViewHolder(ItemViewHolder holder, int position) {
Item item=itemList.get(position);
holder.timeDifference.setText(item.timeDifference);
holder.title.setText(item.title);
holder.timeCounter.setText(item.timeCounter);
holder.externalSystem.setText(item.externalSystem);
holder.type.setText(item.type);
holder.project.setText(item.project);
MY IDEA
"holder.timeDifference.setTextColor(Color.parseColor(item.color));"
}
#Override
public int getItemCount() {
if(itemList!=null)
return itemList.size();
else
return 0;
}
public static class ItemViewHolder extends RecyclerView.ViewHolder
{
public CardView cardViewItem;
public TextView title;
public TextView project;
public TextView externalSystem;
public TextView timeDifference;
public TextView timeCounter;
public TextView type;
public ItemViewHolder(View itemView)
{
super(itemView);
cardViewItem=(CardView)itemView.findViewById(R.id.card_view_item);
title=(TextView)itemView.findViewById(R.id.title);
project=(TextView)itemView.findViewById(R.id.project);
externalSystem=
(TextView)itemView.findViewById(R.id.external_system);
timeDifference=
(TextView)itemView.findViewById(R.id.time_difference);
timeCounter=(TextView)itemView.findViewById(R.id.time_counter);
type=(TextView)itemView.findViewById(R.id.type);
}
EDIT: I think I found a way, but I don't know if it's the best one
The solution would involve changing your Item class a little.
Your problem is that you're not passing over the boolean trigger to your RecyclerView.Adapter from your Activity properly. E.g. isEndedBoolean's value to know what state the item is in. You have the right idea in the use of all three classes.
What I would suggest do is create a constructor in your Item class passing the values from your Activity to be used in your adapter. I feel it's easier to use getters and setters rather than assigning the variables straight from code like you have.
So let's begin,
boolean isEnded;
if(!endedAt.equals("null")) {
endedAt = endedAt.substring(endedAt.indexOf("T") + 1, endedAt.lastIndexOf(":"));
isEnded=true;
} else {
endedAt="ongoing";
isEnded=false;
}
String timeDifference = createdAt+" - "+endedAt;
if(externalSystem.equals("null")) {
externalSystem="";
} else {
externalSystem = externalSystem;
}
Log.i("attr",externalSystem);
items.add(new ItemModel(isEnded, timeDifference, externalSystem);
itemAdapter=new ItemAdapter(this, items);
recyclerView.setAdapter(itemAdapter);
itemAdapter.notifyDataSetChanged();
You'll notice how I'm adding a new ItemModel for each row of variables inside the RecyclerView to the array and then passing it that array to the Adapter. This is so that it's easier to know what variables are being passed to the Model and thus the corresponding row at the position inside the Adapter.
An example of the ItemModel class would look something like:
public class ItemModel {
// Getter and Setter model for recycler view items
private boolean isEnded;
private String timeDifference;
private String externalSystem;
//other variables, title etc etc
public ItemModel(boolean isEnded, String timeDifference, String externalSystem) {
this.isEnded = isEnded;
this.timeDifference = timeDifference;
this.externalSystem = externalSystem;
//only pass to the model if you can access it from code above otherwise to assign the variables statically like you have.
}
public boolean getIsEnded() {return isEnded;}
public String getTimeDifference() {return timeDifference;}
public String getExternalSystem() { return externalSystem; }
}
The information above is just a guideline for you to create a more efficient model framework to pass the data rather than using static variables.
Now to solve your problem you need to check if (item.getIsEnded()) and then change the text color corresponding to that if condition.
RecyclerView.Adapter onBindViewHolder would look like:
#Override
public void onBindViewHolder(ItemViewHolder holder, int position) {
ItemModel item =itemList.get(position);
holder.timeDifference.setText(item.getTimeDifference());
holder.title.setText(item.title);
holder.timeCounter.setText(item.timeCounter);
holder.externalSystem.setText(item.getExternalSystem());
holder.type.setText(item.type);
holder.project.setText(item.project);
if (item.getIsEnded() {
holder.timeDifference.setTextColor(item.color);
} else {
holder.timeDifference.setTextColor(item.color);
}
}
The purpose of the Adapter is to inflate a layout, bind components to that layout and perform functionality to the items corresponding to the layout at the dedicated position. You need to know which item in your list is in which state, you won't be able to do that from your Activity alone. Be mindful of how useful the Adapter is in keeping the code from your Activity separate from the actual activity of your RecyclerView.
I have a RecyclerView that will contain list of item retrieved from the internet. So at first, the list will be empty. After the data retrieved from the internet, it will update the list and call notifyDataSetChanged().
I can adapt the data into the RecyclerView just fine. But, I have an ImageButton for each of item which has different Image if it's clicked. If I initialize the flags array inside onBindViewHolder, each time I scrolled the RecyclerView, the flag array will be reinitialize to false. If I initialize it in the Adapter constructor, it will be 0 index since the list will be empty at first. Where should I put array initializing in adapter if the data will come at some amount of time later?
Below is my code, but the flag array (isTrue) is always reinitialize each time I scrolled my RecyclerView.
public class SomethingAdapter extends RecyclerView.Adapter<SomethingAdapter.ViewHolder> {
private ArrayList<String> someList;
private boolean[] isTrue;
public static class ViewHolder extends RecyclerView.ViewHolder {
public TextView someText;
public ImageButton someButton;
public ViewHolder(View v) {
super(v);
someText = (TextView) v.findViewById(R.id.text);
someButton = (ImageButton) v.findViewById(R.id.button);
}
}
public SomethingAdapter(ArrayList<String> someList) {
this.someList = someList;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.some_layout, parent, false);
return new ViewHolder(v);
}
#Override
public void onBindViewHolder(final ViewHolder viewHolder, final int position) {
//TODO: This thing will make isTrue always reinitialize if scrolled
this.isTrue = new boolean[someList.getResults().size()];
viewHolder.someText.setText(someList.get(position));
if (isTrue[position]) {
viewHolder.someButton.setImageResource(R.drawable.button_true);
} else {
viewHolder.someButton.setImageResource(R.drawable.button_false);
}
viewHolder.someButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (isTrue[position]) {
//Connect to the internet and if response is positive {
//isTrue[position] = false;
//viewHolder.someButton.setImageResource(R.drawable.button_false);
//}
} else {
//Connect to the internet and if response is positive {
//isTrue[position] = true;
//viewHolder.someButton.setImageResource(R.drawable.button_true);
//}
}
}
});
}
#Override
public int getItemCount() {
return someList.size();
}
Initialize it when you add items to someList.
Also, don't add click listener in your onBind, create it in onCreateViewHolder. You cannot use position in the click callback, instead you should be using ViewHolder#getAdapterPosition.
See docs for details:
https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#onBindViewHolder(VH, int)
I thought they were the same, but they're not. The following code gives an indexOutOfBounds exception when I try to access the "position" index of my dataset, in this case a list of a model I created called Task:
public class TaskAdapter extends RecyclerView.Adapter<TaskAdapter.TaskViewHolder> {
private List<Task> taskList;
private TaskAdapter thisAdapter = this;
// cache of views to reduce number of findViewById calls
public static class TaskViewHolder extends RecyclerView.ViewHolder {
protected TextView taskTV;
protected ImageView closeBtn;
public TaskViewHolder(View v) {
super(v);
taskTV = (TextView)v.findViewById(R.id.taskDesc);
closeBtn = (ImageView)v.findViewById(R.id.xImg);
}
}
public TaskAdapter(List<Task> tasks) {
if(tasks == null)
throw new IllegalArgumentException("tasks cannot be null");
taskList = tasks;
}
// onBindViewHolder binds a model to a viewholder
#Override
public void onBindViewHolder(TaskViewHolder taskViewHolder, int pos) {
final int position = pos;
Task currTask = taskList.get(pos);
taskViewHolder.taskTV.setText(currTask.getDescription());
**taskViewHolder.closeBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.d("TRACE", "Closing task at position " + position);
// delete from SQLite DB
Task taskToDel = taskList.get(position);
taskToDel.delete();
// updating UI
taskList.remove(position);
thisAdapter.notifyItemRemoved(position);
}
});**
}
#Override
public int getItemCount() {
//Log.d("TRACE", taskList.size() + " tasks in DB");
return taskList.size();
}
// inflates row to create a viewHolder
#Override
public TaskViewHolder onCreateViewHolder(ViewGroup parent, int pos) {
View itemView = LayoutInflater.from(parent.getContext()).
inflate(R.layout.list_item, parent, false);
Task currTask = taskList.get(pos);
//itemView.setBackgroundColor(Color.parseColor(currTask.getColor()));
return new TaskViewHolder(itemView);
}
}
Deleting from my recyclerview gives unexpected results sometimes. Sometimes the element ahead of the one clicked is deleted, other times an indexOutOfBounds exception occurs at "taskList.get(position)".
Reading https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html and https://developer.android.com/training/material/lists-cards.html did not give me any more insight into why this was happening and how to fix it.
It looks like RecyclerView recycles the rows, but I wouldn't expect an indexoutofbounds exception using a smaller subset of numbers to index my list.
RecyclerView does not rebind views when their positions change (for obvious performance reasons).
For example, if your data set looks like this:
A B C D
and you add item X via
mItems.add(1, X);
notifyItemInserted(1, 1);
to get
A X B C D
RecyclerView will only bind X and run the animation.
There is a getPosition method in ViewHolder but that may not match adapter position if you call it in the middle of an animation.
If you need the adapter position, your safest option is getting the position from the Adapter.
update for your comment
Add a Task field to the ViewHolder.
Change onCreateViewHolder as follows to avoid creating a listener object on each rebind.
// inflates row to create a viewHolder
#Override
public TaskViewHolder onCreateViewHolder(ViewGroup parent, int type) {
View itemView = LayoutInflater.from(parent.getContext()).
inflate(R.layout.list_item, parent, false);
final TaskViewHolder vh = new TaskViewHolder(itemView);
taskViewHolder.closeBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// delete from SQLite DB
Task taskToDel = vh.getTask();
final int pos = taskList.indexOf(taskToDel);
if (pos == -1) return;
taskToDel.delete();
// updating UI
taskList.remove(pos);
thisAdapter.notifyItemRemoved(pos);
}
});
}
so in your on bind method, you do
// onBindViewHolder binds a model to a viewholder
#Override
public void onBindViewHolder(TaskViewHolder taskViewHolder, int pos) {
Task currTask = taskList.get(pos);
taskViewHolder.setTask(currTask);
taskViewHolder.taskTV.setText(currTask.getDescription());
}
Like yigit said, RecyclerView works like that:
A B C D
and you add item X via
mItems.add(1, X);
notifyItemInserted(1, 1);
you get
A X B C D
Using holder.getAdapterPosition() in onClickListener() will give you the right item from dataset to be removed, not the "static" view position. Here's the doc about it onBindViewHolder
Why dont you use a public interface for the button click and controle the action in the MainActivity.
In your adapter add:
public interface OnItemClickListener {
void onItemClick(View view, int position, List<Task> mTaskList);
}
and
public OnItemClickListener mItemClickListener;
// Provide a suitable constructor (depends on the kind of dataset)
public TaskAdapter (List<Task> myDataset, OnItemClickListener mItemClickListener) {
this.mItemClickListener = mItemClickListener;
this.mDataset = mDataset;
}
plus the call in the ViewHolder class
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public ViewHolder(View v) {
super(v);
...
closeBtn = (ImageView)v.findViewById(R.id.xImg);
closeBtn.setOnClickListener(this);
}
#Override
public void onClick(View v) {
// If not long clicked, pass last variable as false.
mItemClickListener.onItemClick(v, getAdapterPosition(), mDataset);
}
}
In your MainActivity change your adapter to handle the call
// set Adapter
mAdapter = new TaskAdapter(taskList, new TaskAdapter.OnItemClickListener() {
#Override
public void onItemClick(View v, int position) {
if (v.getId() == R.id.xImg) {
Task taskToDel = taskList.get(position);
// updating UI
taskList.remove(position);
thisAdapter.notifyItemRemoved(position);
// remove from db with unique id to use delete query
// dont use the position but something like taskToDel.getId()
taskToDel.delete();
}
}
});
Thanks to #yigit for his answer, his solution mainly worked, I just tweaked it a little bit so as to avoid using vh.getTask() which I was not sure how to implement.
final ViewHolder vh = new ViewHolder(customView);
final KittyAdapter final_copy_of_this = this;
// We attach a CheckChange Listener here instead of onBindViewHolder
// to avoid creating a listener object on each rebind
// Note Rebind is only called if animation must be called on view (for efficiency)
// It does not call on the removed if the last item is checked
vh.done.setChecked(false);
vh.done.setOnCheckedChangeListener(null);
vh.done.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
buttonView.setEnabled(false);
final int pos2 = vh.getAdapterPosition(); // THIS IS HOW TO GET THE UPDATED POSITION
// YOU MUST UPDATE THE DATABASE, removed by Title
DatabaseHandler db = new DatabaseHandler(mContext);
db.remove(mDataSet.get(pos2).getTitle(), fp);
db.close();
// Update UI
mDataSet.remove(pos2);
final_copy_of_this.notifyItemRemoved(pos2);
}
});
Notice instead to get the updated position, you can call vh.getAdapterPosition(), which is the line that will give you the updated position from the underlying dataset rather than the fake view.
This is working for me as of now, if someone knows of a drawback to using this please let me know. Hope this helps someone.
Personally, I don't like this concept of RecyclerViews. Seems like it wasn't thought of completely.
As it was said when removing an item the Recycler view just hides an item. But usually you don't want to leave that item in your collection. When deleting an item from the collection "it shifts its elements towards 0" whereas recyclerView keeps the same size.
If you are calling taskList.remove(position); your position must be evaluated again:
int position = recyclerView.getChildAdapterPosition(taskViewHolder.itemView);