RecyclerView checkbox deselect all? - android

I'm wondering how I'd be able to use a button in another class to deselect all checkboxes in my 3 recyclerviews(Tablayout, one recyclerview per tab). I have saved the checked value in shared pref as shown here:
public class MyRecyclerAdapter extends RecyclerView.Adapter<MyViewHolder> {
Context mContext;
ArrayList<Workout> workout;
SharedPreferences prefs;
int firstSecondOrThird;
int colorResId = R.color.defaultcard;
public MyRecyclerAdapter(Context context, ArrayList<Workout> workout, int thePosition) {
mContext = context;
this.workout = workout;
this.firstSecondOrThird = thePosition;
}
// INITIALIZE HOLDER
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.workout_item, null);
MyViewHolder holder = new MyViewHolder(view);
return holder;
}
//BIND DATA TO VIEWS
#Override
public void onBindViewHolder(MyViewHolder holder, final int position) {
holder.exercise.setText(workout.get(position).getExercise());
holder.percent.setText(workout.get(position).getPercent());
holder.reps.setText(workout.get(position).getReps());
holder.weight.setText(workout.get(position).getWeight());
holder.check1.setOnCheckedChangeListener(null);
final Workout isCheck = workout.get(position);
prefs = mContext.getSharedPreferences("checkState", Context.MODE_PRIVATE);
holder.check1.setChecked(prefs.getBoolean(firstSecondOrThird+"checkState"+position, false));
isCheck.setCheck1(prefs.getBoolean(firstSecondOrThird+"checkState"+position, false));
holder.check1.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
prefs.edit().putBoolean(firstSecondOrThird+"checkState"+position, isChecked).apply();
}
});
}
And here is where I try to clear shared pref, which I thought would also clear all checkmarks:
mButton = (Button) findViewById(R.id.button2);
mButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
prefs.edit().clear();
prefs.edit().apply();
Toast.makeText(getApplicationContext(), "Checkboxes Cleared.", Toast.LENGTH_LONG).show();
}
});
}
Unfortunately, after clicking the button, all checkboxes that were previously selected are still selected. Thanks in advance!
EDIT: I tried to add an if statement to see if that would work, but now it doesn't load the saved checkmarks are all, even when the button isn't clicked... lol
prefs = mContext.getSharedPreferences("checkState", Context.MODE_PRIVATE);
String restoredCheck = prefs.getString("checkState", null);
if (restoredCheck != null) {
holder.check1.setChecked(prefs.getBoolean(firstSecondOrThird+"checkState"+position, false));
} else holder.check1.setChecked(false);

You need to alter the List inside the adapter, then, call ((MyRecyclerAdapter) mMyListView.getAdapter()).notifyDataSetChanged();
For experience, some times depending on what change are made, the adapter do not update with this, in this cases, i create a new adapter with the new list of data, and set to the ListView, after, you may need to call the notifyDataSetChanged() anyway
Edit:
Well, inside your adapter you use a `List', the state change must be done in that List, and after that you notify the adapter that the data was changed.
So you need a method to get the ArrayList and do something like:
mButton = (Button) findViewById(R.id.button2);
mButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
ArrayList<Workout> list = ((MyRecyclerAdapter) MyListView.getAdapter()).getList();
for(Workout workout : list){
workout.setCheck(true);
}
((MyRecyclerAdapter) MyListView.getAdapter()).notifyDataSetChanged();
}
});
}
I'm not able to test right now, but i think it's some like that, like i said, if this don't work, create another adapter with the new data and set him to the ListView.
Ps. I considered you correctly check if the object isChecked and change the checkbox in the ViewHoler

Related

How to fix Checkbox in RecyclerView when no solution works?

I'm trying to set up a checkbox in my recycler view, but I'm facing the common problem of random checks without my control. I check one item and some more random items are being checked at the same time.
I realise this is a common question here, but the solutions I found here don't seem to work. Among many others I was trying to follow different instructions in this thread:
Android RecyclerView checkbox checks itself
but what seems to be the problem is that functions isSelected and setSelected can't be resolved.
My best guess is that the reason for this complication might be that my onCheckedChange action is actually interfaced from the main activity, as you can see at the bottom of the code below. Is that what complicates the code (can't avoid it)?
public LocationRecyclerViewAdapter(List<IndividualLocation> styles,
Context context, ClickListener cardClickListener, OnCheckedChangeListener checkedChangeListener, int selectedTheme) {
this.context = context;
this.listOfLocations = styles;
this.selectedTheme = selectedTheme;
this.clickListener = cardClickListener;
this.onCheckedChangeListener = checkedChangeListener;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
int singleRvCardToUse = R.layout.single_location_map_view_rv_card;
View itemView = LayoutInflater.from(parent.getContext()).inflate(singleRvCardToUse, parent, false);
return new ViewHolder(itemView);
}
public interface ClickListener {
void onItemClick(int position);
}
public interface OnCheckedChangeListener {
void onCheckboxClick(int position);
}
#Override
public int getItemCount() {
return listOfLocations.size();
}
#Override
public void onBindViewHolder(final ViewHolder card, int position) {
final IndividualLocation locationCard = listOfLocations.get(position);
card.nameTextView.setText(locationCard.getName());
card.addressTextView.setText(locationCard.getAddress());
card.hoursTextView.setText(locationCard.getHours());
card.priceTextView.setText(locationCard.getPrice());
card.categoryTextView.setText(locationCard.getCategory());
card.cuisineTextView.setText(locationCard.getCuisine());
card.happyHourTextView.setText(locationCard.getHappyHour());
card.lunchDealTextView.setText(locationCard.getLunchDeal());
card.websiteTextView.setText("WEBSITE");
card.newPlaceTextView.setText(locationCard.getNewPlace());
card.websiteTextView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String url = locationCard.getWebsite();
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
context.startActivity(intent);
}
});
static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView nameTextView;
TextView addressTextView;
TextView priceTextView;
TextView hoursTextView;
TextView categoryTextView;
TextView cuisineTextView;
TextView happyHourTextView;
TextView lunchDealTextView;
TextView newPlaceTextView;
ConstraintLayout constraintUpperColorSection;
CardView cardView;
ImageView backgroundCircleImageView;
ImageView emojiImageView;
Button websiteTextView;
CheckBox checkBox;
ViewHolder(View itemView) {
super(itemView);
nameTextView = itemView.findViewById(R.id.location_name_tv);
addressTextView = itemView.findViewById(R.id.location_description_tv);
priceTextView = itemView.findViewById(R.id.location_price_tv);
hoursTextView = itemView.findViewById(R.id.location_hours_tv);
backgroundCircleImageView = itemView.findViewById(R.id.background_circle);
emojiImageView = itemView.findViewById(R.id.emoji);
constraintUpperColorSection = itemView.findViewById(R.id.constraint_upper_color);
categoryTextView = itemView.findViewById(R.id.location_type_tv);
cuisineTextView = itemView.findViewById(R.id.location_cuisine_tv);
happyHourTextView = itemView.findViewById(R.id.happyhour_tv);
lunchDealTextView = itemView.findViewById(R.id.lunchdeal_tv);
cardView = itemView.findViewById(R.id.map_view_location_card);
websiteTextView = itemView.findViewById(R.id.website);
newPlaceTextView = itemView.findViewById(R.id.new_place);
cardView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
clickListener.onItemClick(getLayoutPosition());
}
});
checkBox = itemView.findViewById(R.id.checkBox);
checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener(){
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
onCheckedChangeListener.onCheckboxClick(getLayoutPosition());
}
}});
}
#Override
public void onClick(View view) {
}
}
}
I'm sure there is some simple solution to this, but I've been cracking my head over this for way too long now. Thanks!
First of all, you have to check the checkbox programatically, for every item. Your model should hold the data, a simple boolean could do it.
The second is the trickiest one
checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener(){
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if(buttonView.isPressed()){
if (isChecked ) {
onCheckedChangeListener.onCheckboxClick(holder.getAdapterPosition()());
}}
}});
You have to check if the checkbox is actually checked, in order to avoid confusions,
Inside your onBindViewHolder you have to set your checkbox either checked or unchecked. if true, your checkbox will be selected, else unselected,
because recycler view will be recycled every time you scroll, so you need to make sure you need to tell adapter that checkbox need to be checked or unchecked at that particular position.
why are you using getLayoutPosition() for getting checkbox position
use like this holder.getAdapterPosition()
checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener(){
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
onCheckedChangeListener.onCheckboxClick(holder.getAdapterPosition()());
}
}});
and move this listener to your onBindViewHolder()
Your data model should have a boolean variable isChecked. You can also give it an default value false. Now, in your adapter function onBindViewHolder, you should set value of your checkbox ex.
card.checkBox.setChecked(locationCard.getIsChecked())
Also, in your onCheckedChanged you should update your data
locationCard.setIsChecked(isChecked)
Now each time your RecyclerView Adapter reuses and list item, it will set the value of its checkbox correctly. Hope this helps.

Toggle button in ListView changes State on Scrolling

I think I have searched through all the posts relating to my question but could not find a solution to my problem. I have A ListView with two textviews and one Toggle button. Toggle button Changes its state when I scroll up or down (go out of view). I am using viewholder (suggested on the forum) that does make the button to retain its state but then the recycling property comes into play and mess up my application i.e multiple toggle buttons change their state on one click.
What I want?
I want my toggle button to retain its previous state when it goes out of view and comes back plus when I click one toggle button only that toggle button should work.
Here is my Code for Listview (pardon me for not putting much comments)
public class ActivityB extends AppCompatActivity {
ListView list;
Button edit;
public String text= "";
public SharedPreferences.Editor editor;
public SharedPreferences sharedPreferences;
private ArrayList<String> data = new ArrayList<String>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_b);
list = (ListView) findViewById(R.id.listView);
generateListContent();
list.setAdapter(new MyListadapter(this,R.layout.constraint,data));
edit= (Button) findViewById(R.id.button);
sharedPreferences=getSharedPreferences("MyData",Context.MODE_PRIVATE);
editor=sharedPreferences.edit();
text=sharedPreferences.getString("Text","Null");
//Toast.makeText(this,text,Toast.LENGTH_LONG).show();
}
public void generateListContent() {
for (int i = 0; i < 11; i++) {
data.add("This is item no " + i);
}
}
private class MyListadapter extends ArrayAdapter<String> {
private int layout;
ArrayList<String> list;
public MyListadapter(#NonNull Context context, #LayoutRes int resource, #NonNull List<String> objects) {
super(context, resource, objects);
layout=resource;
list= (ArrayList<String>) objects;
}
#NonNull
#Override
public View getView(final int position, #Nullable View convertView, #NonNull ViewGroup parent) {
ViewHolder mainviewHolder=null;
if(convertView==null){
LayoutInflater inflater=LayoutInflater.from(getContext());
convertView= inflater.inflate(layout,parent,false);
final ViewHolder viewHolder = new ViewHolder();
viewHolder.title= (TextView) convertView.findViewById(R.id.textView);
viewHolder.lastone= (TextView) convertView.findViewById(R.id.textView2);
viewHolder.button= (ToggleButton) convertView.findViewById(R.id.toggleButton);
//mainviewHolder.title.setText(getItem(position));
viewHolder.button.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
buttonView.setChecked(true);
Toast.makeText(getContext(), "Button is On" + position , Toast.LENGTH_LONG).show();
} else {
buttonView.setChecked(false);
Toast.makeText(getContext(), "Button is OFF" + position + buttonView.getId(),Toast.LENGTH_LONG).show();
}
}
});
convertView.setTag(viewHolder);
}
else{
mainviewHolder = (ViewHolder) convertView.getTag();
mainviewHolder.title.setText(getItem(position));
}
return convertView;
}
}
public class ViewHolder{
TextView title;
TextView lastone;
ToggleButton button;
}
}
check this...
Recyclerview Changing Items During Scroll
if above does not work....
Method 1
things you can try...
1.check button status outside toggle method....so that every time the listviewe loaded it can assign a value rather no value.
create a method togglebtn(viewHolder) // pass the instance of this perticualr item
Inside togglebtn....modify the toggle btn by reference with the instance.
togglebtn(viewHolder v)
{
}
Method 2
create a public data structure to hold the status of the button....preferebaly hasmap to store item no+check status....then check this value every time and update.
Feel free to ask.
Instead of creating array list of strings, create a custom object something like below.
Class A {
public String title;
public boolean isChecked;
}
Set correct value to isChecked(true/false) depending on toggle button state.
Also add one more line inside getview()
button.setChecked(list.get(position).isChecked)
Thanks Everyone for sparing your time and answering my question.
With these answers and a little help from forum' other posts, I found a solution.
Since I have a limited number of views so i removed the if/else condition on layout inflater. This solved the reuse of position variable. Now every view object has a unique position. Secondly the random button status behavior is resolved by saving the toggle button state in a separate array and keeping it outside my getview() method.

Working with Long Click Listener with recycler-android

i am working on a notapad like android app project. i which i have implemented recycler.
My project contains NotedAdaper class that extends RecyclerView.Adapter<NotesAdapter.ViewHolder>
in that class using the below code i used click listener,
public class NotesAdapter extends RecyclerView.Adapter<NotesAdapter.ViewHolder> {
private List<Notes> mNotes;
private Context mContext;
public NotesAdapter(Context context, List<Notes> notes) {
mNotes = notes;
mContext = context;
}
#Override
public NotesAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Context context = parent.getContext();
LayoutInflater inflater = LayoutInflater.from(context);
// Inflate the custom layout
View notesView = inflater.inflate(R.layout.items_notes, parent, false);
// Return a new holder instance
ViewHolder viewHolder = new ViewHolder(notesView);
return viewHolder;
}
// Easy access to the context object in the recyclerview
private Context getContext() {
return mContext;
}
#Override
public void onBindViewHolder(NotesAdapter.ViewHolder viewHolder, final int position) {
// Get the data model based on position
Notes notes = mNotes.get(position);
// Set item views based on your views and data model
TextView textView = viewHolder.preTitle;
textView.setText(notes.getTitle());
TextView textView1 = viewHolder.preText;
textView1.setText(notes.getText());
String color=notes.getColor();
CardView preCard=viewHolder.preCard;
preCard.setBackgroundColor(Color.parseColor(color));
ImageView img = viewHolder.preImage;
img.setVisibility(View.GONE);
viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Notes notes = mNotes.get(position);
Intent intent = new Intent(view.getContext(),EditNote.class);
Bundle bundle = new Bundle();
bundle.putSerializable("DATA",notes);
intent.putExtras(bundle);
getContext().startActivity(intent);
Toast.makeText(getContext(), "Recycle Click" + position+" ", Toast.LENGTH_SHORT).show();
}
});
}
// Returns the total count of items in the list
#Override
public int getItemCount() {
return mNotes.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
// Your holder should contain a member variable
// for any view that will be set as you render a row
public RobotoTextView preTitle, preText;
public ImageView preImage;
public CardView preCard;
public ViewHolder(View itemView) {
super(itemView);
preTitle = (RobotoTextView) itemView.findViewById(R.id.preTitle);
preText = (RobotoTextView) itemView.findViewById(R.id.preText);
preImage=(ImageView) itemView.findViewById(R.id.preImage);
preCard=(CardView) itemView.findViewById(R.id.preCard);
}
}}
And its absolutely working find. on clicking of a item in recycler, it retrieves the data using position of that item. and showing in another activity.
just like, suppose a activity shows the list of notes created by user. and clicking on any note, it shows the full content of that note.
but now i want to implement Long click listener on the item. and get the position.
so that, i used the following code ...
viewHolder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View view) {
Notes notes = mNotes.get(position);
Toast.makeText(getContext(), "long Click" + position+" ", Toast.LENGTH_SHORT).show();
return false;
}
});
so, its also working.
but what i want is, on long click, it should only show that Toast.
but its not only showing the long click toast. but also recognising click listener and after showing the toast>> "Long click: ..." it executing the the code written for single click event.
n i dont want it.
both listeners should work separately.
but why its executing single click after long click??? any idea???
Am i making mistake anywhere?
So, the following changes in my code, help me to achieve my output.
1) The method onBindViewHolder is called every time when you bind your view with data. So there is not the best place to set click listener. You don't have to set OnClickListener many times for the one View. Thats why, i wrote click listeners in ViewHolder, (actually that was not my question, but i read somewhere that it would be the best practice, thats why i am following it)
like this,
public static class ViewHolder extends RecyclerView.ViewHolder {
// Your holder should contain a member variable
// for any view that will be set as you render a row
public ImageView preImage;
public CardView preCard;
// We also create a constructor that accepts the entire item row
// and does the view lookups to find each subview
public ViewHolder(final View itemView) {
// Stores the itemView in a public final member variable that can be used
// to access the context from any ViewHolder instance.
super(itemView);
itemView.findViewById(R.id.preTitle);
preImage=(ImageView) itemView.findViewById(R.id.preImage);
preCard=(CardView) itemView.findViewById(R.id.preCard);
itemView.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View view) {
int p=getLayoutPosition();
System.out.println("LongClick: "+p);
return true;// returning true instead of false, works for me
}
});
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
int p=getLayoutPosition();
Notes notes = mNotes.get(p);
Toast.makeText(getContext(), "Recycle Click" + p +" ", Toast.LENGTH_SHORT).show();
}
});
}
}
You may notice that, in onLongClick, i have returned "true", bydefault it was "false".
and this change works for me.
just make onLongClick(View v) returns return true instead of return false
this solved my problem it should solve yours too
i think you should set both the listeners from ViewHolder class.
itemView.setOnClickListener(...);
itemView.setOnLongClickListener(...);
And call getAdapterPosition() from ViewHolder to get the adapter position of the item.
You can checkout the following resource.
https://www.bignerdranch.com/blog/recyclerview-part-1-fundamentals-for-listview-experts/
I think this is an easier way to implement onClick and longClickListeners to RecyclerViews. Here's my code snippet. I've cut out unnecessary codes from here.
public class PrescriptionAdapter extends RecyclerView.Adapter<PrescriptionAdapter.ViewHolder> {
static final String TAG = "RecyclerViewAdapterMedicineFrequency";
ArrayList<Prescription> pdata;
Context context;
OnItemClickListener onItemClickListener;
OnItemLongClickListener onItemLongClickListener;
public PrescriptionAdapter(Context context, ArrayList<Prescription> presData, OnItemClickListener onItemClickListener, OnItemLongClickListener onItemLongClickListener) {
this.pdata = presData;
this.context = context;
this.onItemClickListener = onItemClickListener;
this.onItemLongClickListener = onItemLongClickListener;
}
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {
TextView pName, totalMeds;
ImageView pImage;
OnItemClickListener onItemClickListener;
OnItemLongClickListener onItemLongClickListener;
public ViewHolder(View itemView, OnItemClickListener onItemClickListener, OnItemLongClickListener onItemLongClickListener) {
super(itemView);
pName = (TextView) itemView.findViewById(R.id.prescriptionName);
totalMeds = (TextView) itemView.findViewById(R.id.totalMeds);
pImage = (ImageView) itemView.findViewById(R.id.prescriptionImage);
this.onItemClickListener = onItemClickListener;
this.onItemLongClickListener = onItemLongClickListener;
itemView.setOnClickListener(this);
itemView.setOnLongClickListener(this);
}
#Override
public void onClick(View v) {
onItemClickListener.onItemClick(getAdapterPosition());
}
#Override
public boolean onLongClick(View v) {
onItemLongClickListener.onItemLongClick(getAdapterPosition());
return true;
}
}
public interface OnItemClickListener {
void onItemClick(int i);
}
public interface OnItemLongClickListener {
void onItemLongClick(int i);
}
}

How should I initialize an array inside RecyclerView Adapter which has the size of a list while the list will not available on Adapter construction?

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)

How is the position of a RecyclerView adapter related to the index of its dataset?

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

Categories

Resources