RecyclerView messed up data when scrolling - java

Having a problem when scrolling RecyclerView after scrolling down and up. The idea is to change elements color, but when I scroll down everything is great and when the scroll goes up - the elements, which are shouldn't be colored are changing color.
Here's my adapter:
public class NotificationsAdapter extends RecyclerView.Adapter<NotificationsAdapter.ViewHolder> {
private NotificationData notificationData;
private Context mContext;
private ArrayList<NotificationData> infromationList = new ArrayList<>();
public NotificationsAdapter(Context context, ArrayList<NotificationData> infromationList) {
this.infromationList = infromationList;
this.mContext = context;
}
#Override
public NotificationsAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemLayoutView;
ViewHolder viewHolder;
itemLayoutView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.notification_single_item, parent, false);
viewHolder = new ViewHolder(itemLayoutView, viewType);
return viewHolder;
}
#Override
public void onBindViewHolder(NotificationsAdapter.ViewHolder holder, int position) {
notificationData = infromationList.get(position);
holder.notificationDate.setText(convertDate(notificationData.getDate()));
holder.notificationStatus.setText(notificationData.getNotificationStatus());
holder.orderDescription.setText(notificationData.getNotificationLabel());
if ("true".equals(notificationData.getReadStatus())) {
holder.root.setBackgroundColor(mContext.getResources().getColor(R.color.white));
holder.notificationStatus.setTypeface(Typeface.create("sans-serif-light", Typeface.NORMAL));
}
}
#Override
public int getItemCount() {
return (null != infromationList ? infromationList.size() : 0);
}
public static class ViewHolder extends RecyclerView.ViewHolder {
public TextView notificationDate;
public TextView notificationStatus;
public TextView orderDescription;
public LinearLayout root;
public ViewHolder(View itemView, int position) {
super(itemView);
notificationDate = (TextView) itemView.findViewById(R.id.notificationDate);
notificationStatus = (TextView) itemView.findViewById(R.id.notificationStatus);
orderDescription = (TextView) itemView.findViewById(R.id.orderDescription);
root = (LinearLayout) itemView.findViewById(R.id.root);
}
}
private String convertDate(String date) {
String convertedDate;
String[] parts = new String[2];
parts = date.split("T");
date = parts[0];
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-mm-dd");
Date testDate = null;
try {
testDate = sdf.parse(date);
}catch(Exception ex){
ex.printStackTrace();
}
SimpleDateFormat formatter = new SimpleDateFormat("dd.mm.yyyy");
convertedDate = formatter.format(testDate);
return convertedDate;
}
}

I had the same problem and the only solution I found for this is:
holder.setIsRecyclable(false);
Your recycler will not recycle anymore so the items will be the same when you scroll, and if you want to delete some item do not use notifyitemRemoved(position), use notifyDataSetChanged() instead.

Add setHasStableIds(true); in your adapter constructor and
Override these two methodes in adapter.
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getItemViewType(int position) {
return position;
}

There is problem in your onBindViewHolder(...), should be:
if ("true".equals(notificationData.getReadStatus())) {
holder.root.setBackgroundColor(mContext.getResources().getColor(R.color.white));
holder.notificationStatus.setTypeface(Typeface.create("sans-serif-light", Typeface.NORMAL));
}
else {
holder.root.setBackgroundColor(yourDefaultColor);
holder.notificationStatus.setTypeface(yourDefaultTypeface);
}

#Override
public void onBindViewHolder(final MyViewHolder holder, int position) {
final UserData userdata = userdataList.get(position);
holder.setIsRecyclable(false);
holder.name.setText(userdata.getName());
holder.active.setChecked(userdata.getActive());
String userPic = userdata.getPic();
holder.active.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener(){
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked){
userdata.setActive(isChecked);
}
});
}

onBindHolder called several times as Recycler View needs a view unless new one. So each time you set visilibity in child views, other views states are also changes.
Whenever you scroll up and down, these views are getting re-drawed with wrong visibility options so always specify both the conditions cause recycler view doesn't know the previous state/conditions/values of our widgets.
Solution :
If in If block you set visibility of any android widget.setVisibility(View.Gone) then in else block you have to set it's visibility opposite value like widget.setVisibility(View.Visible) to overcome the above problem.
#Override
public void onBindViewHolder(ViewHolder viewHolder, int i) {
viewHolder.tvName.setText(ModelCategoryProducts.name.get(i));
viewHolder.tvPrice.setText("Rs."+String.format("%.2f", Float.parseFloat(ModelCategoryProducts.price.get(i))));
if(ModelCategoryProducts.special_price.get(i).equals("null")) {
viewHolder.tvSpecialPrice.setVisibility(View.GONE); // here visibility is gone and in else it's opposite visibility i set.
viewHolder.tvPrice.setTextColor(Color.parseColor("#ff0000"));
viewHolder.tvPrice.setPaintFlags(0);// here paint flag is 0 and in else it's opposite flag that i want is set.
}else if(!ModelCategoryProducts.special_price.get(i).equals("null")){
viewHolder.tvPrice.setTextColor(Color.parseColor("#E0E0E0"));
viewHolder.tvSpecialPrice.setVisibility(View.VISIBLE);
viewHolder.tvSpecialPrice.setText("Rs." + String.format("%.2f", Float.parseFloat(ModelCategoryProducts.special_price.get(i))));
viewHolder.tvPrice.setPaintFlags(viewHolder.tvPrice.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
}
if (!ModelCategoryProducts.image_url.get(i).isEmpty()) {
Picasso.with(context)
.load(ModelCategoryProducts.image_url.get(i))
.into(viewHolder.ivProduct);
}
viewHolder.setClickListener(new ItemClickListener() {
#Override
public void onClick(View view, int position, boolean isLongClick) {
if (isLongClick) {
// Toast.makeText(context, "#" + position + " - " + ModelCategoryProducts.name.get(position) + " (Long click)", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context, "#" + position + " - " + ModelCategoryProducts.name.get(position), Toast.LENGTH_SHORT).show();
Intent i = new Intent(context, ProductDetail.class);
i.putExtra("position",position);
i.putExtra("flagHlvCheck", 5);
context.startActivity(i);
}
}
});
}

Try adding this in the adapter.
#Override
public int getItemViewType(int position)
{
return position;
}

If someone might face issues with some of the fields in the viewholder getting random values, then try to set all the fields with atleast any default value.

#Override
public DataObjectHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.custom_layout, parent, false);
DataObjectHolder dataObjectHolder = new DataObjectHolder(view);
dataObjectHolder.setIsRecyclable(false);
return dataObjectHolder;
}

The best way is indicate an ArrayList for example as a Model and have some parameters and define setter and getter for that.
package com.test.mohammaddvi.snappfood.Model;
public class OfferList {
private boolean visibilityOrder;
private int number;
public OfferList(int number, boolean visibilityOrder) {
this.number=number;
this.visibilityOrder=visibilityOrder;
}
public boolean isVisibilityOrder() {
return visibilityOrder;
}
public void setVisibilityOrder(boolean visibilityOrder) {
this.visibilityOrder = visibilityOrder;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
}
and set the the variables as where you want and for get you must do it in onBindViewHolder of your recyclerview Adapter:
if (offerList.isVisibilityOrder()) {
holder.foodMinusButton.setVisibility(View.VISIBLE);
holder.foodOrderNumber.setText(offerList.getNumber() + "");
holder.foodOrderNumber.setVisibility(View.VISIBLE);
} else {
holder.foodMinusButton.setVisibility(View.INVISIBLE);
}
and indicate it your recyclerview adapter:
public class RecyclerViewMenuFragmentAdapter extends RecyclerView.Adapter<RecyclerViewMenuFragmentAdapter.SingleItemInMenuFragment> {
private ArrayList<Food> foodList;
private Context mContext;
private List<OfferList> offers;
public RecyclerViewMenuFragmentAdapter(ArrayList<Food> foodList, Context mContext, List<OfferList> offers) {
this.foodList = foodList;
this.mContext = mContext;
this.offers = offers;
}

class AnyRVAdapter: androidx.recyclerview.widget.RecyclerView.Adapter<AnyRVAdapter.MViewHolder>() {
// put saver outside viewholder
val saveLayId = mutableListOf<Int>()
inner class MViewHolder(itemView: View) :
androidx.recyclerview.widget.RecyclerView.ViewHolder(itemView) {
fun bindModel(d: TesListModel.MList, position:Int) {
// concept here
val showedId= saveLayId.find { s -> s == layoutPosition}
if (idClicked == null) {
// save the layout id
lyClicked.visibility = View.VISIBLE
saveLayId.add(layoutPosition)
} else {
// remove the layout id
lyClicked.visibility = View.INVISIBLE
saveLayId.remove(layoutPosition)
}
}
}
but i think this code is heavy if you use for large data set.

Guys this has worked for me..
override fun setHasStableIds(hasStableIds: Boolean) {
setHasStableIds(true)
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getItemViewType(position: Int): Int {
return position
}

Related

onBindViewHolder repeats the results [duplicate]

Here's the XML for my items inside the RecyclerView
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="#+id/cvItems"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:layout_margin="2dp"
card_view:cardElevation="0dp"
card_view:contentPadding="0dp"
card_view:cardBackgroundColor="#FFFFFF"
>
<LinearLayout
android:orientation="horizontal"
android:layout_height="fill_parent"
android:layout_width="fill_parent">
<TextView
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="0.8"
android:id="#+id/tvContent"
android:textSize="15dp"
android:paddingLeft="5dp"
android:paddingRight="5dp" />
<CheckBox
android:id="#+id/cbSelect"
android:layout_width="0dip"
android:layout_weight="0.2"
android:layout_height="match_parent"
android:button="#drawable/cb_checked"
android:gravity="center_horizontal"
android:textAlignment="center"
android:layout_gravity="center_horizontal" />
</LinearLayout>
</android.support.v7.widget.CardView>
And here's the RecyclerView adapter that inflate the layout above for each of its items:
public class AdapterTrashIncome extends RecyclerView.Adapter<AdapterTrashIncome.ViewHolder> {
private ArrayList<ObjectIncome> myItems = new ArrayList<>();
public AdapterTrashIncome(ArrayList<ObjectIncome> getItems, Context context){
try {
mContext = context;
myItems = getItems;
}catch (Exception e){
Log.e(FILE_NAME, "51: " + e.toString());
e.printStackTrace();
}
}
public class ViewHolder extends RecyclerView.ViewHolder {
public TextView tvContent;
public CheckBox cbSelect;
public ViewHolder(View v) {
super(v);
tvContent = (TextView) v.findViewById(R.id.tvContent);
cbSelect = (CheckBox) v.findViewById(R.id.cbSelect);
}
}
#Override
public void onBindViewHolder(ViewHolder holder, final int position) {
final ObjectIncome objIncome = myItems.get(position);
String content = "<b>lalalla</b>";
holder.tvContent.setText(Html.fromHtml(content));
}
}
The problem is, let's say I have 10 items inside the RecyclerView. When I checked the checkbox on item 1,2,3 then I scroll down the RecyclerView, suddenly some of the other items eg items 8,9 is checked. And when I scroll up again, item 1 and 3 is checked but not item 2. Any idea why this happen?
That's an expected behavior. You are not setting your checkbox selected or not. You are selecting one and View holder keeps it selected. You can add a boolean variable into your ObjectIncome object and keep your item's selection status.
You may look at my example. You can do something like that:
public class AdapterTrashIncome extends RecyclerView.Adapter<AdapterTrashIncome.ViewHolder> {
private ArrayList<ObjectIncome> myItems = new ArrayList<>();
public AdapterTrashIncome(ArrayList<ObjectIncome> getItems, Context context){
try {
mContext = context;
myItems = getItems;
}catch (Exception e){
Log.e(FILE_NAME, "51: " + e.toString());
e.printStackTrace();
}
}
public class ViewHolder extends RecyclerView.ViewHolder {
public TextView tvContent;
public CheckBox cbSelect;
public ViewHolder(View v) {
super(v);
tvContent = (TextView) v.findViewById(R.id.tvContent);
cbSelect = (CheckBox) v.findViewById(R.id.cbSelect);
}
}
#Override
public void onBindViewHolder(ViewHolder holder, final int position) {
final ObjectIncome objIncome = myItems.get(position);
String content = "<b>lalalla</b>";
holder.tvContent.setText(Html.fromHtml(content));
//in some cases, it will prevent unwanted situations
holder.cbSelect.setOnCheckedChangeListener(null);
//if true, your checkbox will be selected, else unselected
holder.cbSelect.setChecked(objIncome.isSelected());
holder.cbSelect.setOnCheckedChangeListener(new OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
//set your object's last status
objIncome.setSelected(isChecked);
}
});
}
}
In short, its because of recycling the views and using them again!
how can you avoid that :
1.In onBindViewHolder check whether you should check or uncheck boxes.
don't forget to put both if and else
if (...)
holder.cbSelect.setChecked(true);
else
holder.cbSelect.setChecked(false);
Put a listener for check box! whenever its checked statues changed, update the corresponding object too in your myItems array ! so whenever a new view is shown, it read the newest statue of the object.
Just add two override methods of RecyclerView
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getItemViewType(int position) {
return position;
}
Use this only if you have limited number of items in your RecyclerView.
I tried using boolean value in model and keep the CheckBox status, but it did not help in my case. What worked for me is this.setIsRecyclable(false);
public class ComponentViewHolder extends RecyclerView.ViewHolder {
public MyViewHolder(View itemView) {
super(itemView);
...
this.setIsRecyclable(false);
}
More explanation on this can be found here
NOTE: This is a workaround. To use it properly you can refer the document which states
Calls to setIsRecyclable() should always be paired (one call to setIsRecyclabe(false) should always be matched with a later call to setIsRecyclable(true)). Pairs of calls may be nested, as the state is internally reference-counted.
I don't know how to do this in code, if someone can provide more code on this.
You can use Model class to keep track of each RecyclerView item's CheckBox. Full reference is from : RecyclerView Checkbox Android
setTag and getTag is used to keep track of CheckBox status. Check full reference link for more information. It also teaches how to send checked items to Next Activity.
Make Model class:
public class Model {
private boolean isSelected;
private String animal;
public String getAnimal() {
return animal;
}
public void setAnimal(String animal) {
this.animal = animal;
}
public boolean getSelected() {
return isSelected;
}
public void setSelected(boolean selected) {
isSelected = selected;
}
}
Create integer.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="btnplusview">1</integer>
<integer name="btnpluspos">2</integer>
</resources>
Finally RecyclerView.Adapter looks like this:
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
public class CustomAdapter extends RecyclerView.Adapter<CustomAdapter.MyViewHolder> {
private LayoutInflater inflater;
public static ArrayList<Model> imageModelArrayList;
private Context ctx;
public CustomAdapter(Context ctx, ArrayList<Model> imageModelArrayList) {
inflater = LayoutInflater.from(ctx);
this.imageModelArrayList = imageModelArrayList;
this.ctx = ctx;
}
#Override
public CustomAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = inflater.inflate(R.layout.rv_item, parent, false);
MyViewHolder holder = new MyViewHolder(view);
return holder;
}
#Override
public void onBindViewHolder(final CustomAdapter.MyViewHolder holder, int position) {
holder.checkBox.setText("Checkbox " + position);
holder.checkBox.setChecked(imageModelArrayList.get(position).getSelected());
holder.tvAnimal.setText(imageModelArrayList.get(position).getAnimal());
// holder.checkBox.setTag(R.integer.btnplusview, convertView);
holder.checkBox.setTag(position);
holder.checkBox.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Integer pos = (Integer) holder.checkBox.getTag();
Toast.makeText(ctx, imageModelArrayList.get(pos).getAnimal() + " clicked!", Toast.LENGTH_SHORT).show();
if (imageModelArrayList.get(pos).getSelected()) {
imageModelArrayList.get(pos).setSelected(false);
} else {
imageModelArrayList.get(pos).setSelected(true);
}
}
});
}
#Override
public int getItemCount() {
return imageModelArrayList.size();
}
class MyViewHolder extends RecyclerView.ViewHolder {
protected CheckBox checkBox;
private TextView tvAnimal;
public MyViewHolder(View itemView) {
super(itemView);
checkBox = (CheckBox) itemView.findViewById(R.id.cb);
tvAnimal = (TextView) itemView.findViewById(R.id.animal);
}
}
}
Using Kotlin the only thing which solved this problem for me was to clear the OnCheckedChangeListener before setting the variable and then create a new OnCheckedChangeListener after checked has been set.
I do the following in my RecyclerView.ViewHolder
task.setOnCheckedChangeListener(null)
task.isChecked = item.status
task.setOnCheckedChangeListener { _: CompoundButton, checked: Boolean ->
item.status = checked
...
do more stuff
...
}
I recommend that not to use checkBox.setOnCheckedChangeListener in RecyclerView.Adapter. Because on scrolling RecyclerView, checkBox.setOnCheckedChangeListener will be fired by adapter. It's not safe. Instead, use checkBox.setOnClickListener to interact with user inputs.
For example:
public void onBindViewHolder(final ViewHolder holder, int position) {
...
holder.checkBoxAdapterTasks.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
boolean isChecked = holder.checkBoxAdapterTasks.isChecked();
if (isChecked) {
// checkBox clicked and checked
} else {
// checkBox clicked and unchecked
}
}
});
}
It might be very late but the simplest of all answers is to assign the check state in bind ViewHolder. RecyclerView will check and apply that state when reusing.
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
binding.checkbox.isChecked = item.isChecked
}
Maintain that state in your class. (Assign a initial default value)
class MyItem {
val isChecked: Boolean = false
}
onClickListener do your stuff and assign the state to class variable. In my case, I have delegate clickListener in view. So, it is like this in Adapter:
binding.checkbox.setOnClickListener {
onClickListener.invoke(item)
}
Then, in view, I am doing this:
val adapter = MyItem { item->
viewModel.checkedContactsList.value?.let { list ->
if (list.contains(item)) {
item.isChecked = false
list.remove(item)
} else {
item.isChecked = true
list.add(item)
}
}
}
In my case this worked.
#Override
public void onViewRecycled(MyViewHolder holder) {
holder.checkbox.setChecked(false); // - this line do the trick
super.onViewRecycled(holder);
}
As stated above, the checked state of the object should be included within object properties. In some cases you may need also to change the object selection state by clicking on the object itself and let the CheckBox inform about the actual state (either selected or unselected). The checkbox will then use the state of the object at the actual position of the given adapter which is (by default/in most cases) the position of the element in the list.
Check the snippet below, it may be useful.
import android.content.Context;
import android.graphics.Bitmap;
import android.net.Uri;
import android.provider.MediaStore;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.ImageView;
import java.io.File;
import java.io.IOException;
import java.util.List;
public class TakePicImageAdapter extends RecyclerView.Adapter<TakePicImageAdapter.ViewHolder>{
private Context context;
private List<Image> imageList;
public TakePicImageAdapter(Context context, List<Image> imageList) {
this.context = context;
this.imageList = imageList;
}
#Override
public TakePicImageAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view= LayoutInflater.from(context).inflate(R.layout.image_item,parent,false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(final TakePicImageAdapter.ViewHolder holder, final int position) {
File file=new File(imageList.get(position).getPath());
try {
Bitmap bitmap= MediaStore.Images.Media.getBitmap(context.getContentResolver(), Uri.fromFile(file));
holder.image.setImageBitmap(bitmap
);
} catch (IOException e) {
e.printStackTrace();
}
holder.selectImage.setOnCheckedChangeListener(null);
holder.selectImage.setChecked(imageList.get(position).isSelected());
holder.selectImage.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
holder.selectImage.setChecked(isChecked);
imageList.get(position).setSelected(isChecked);
}
});
holder.image.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (imageList.get(position).isSelected())
{
imageList.get(position).setSelected(false);
holder.selectImage.setChecked(false);
}else
{
imageList.get(position).setSelected(true);
holder.selectImage.setChecked(true);
}
}
});
}
#Override
public int getItemCount() {
return imageList.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
public ImageView image;public CheckBox selectImage;
public ViewHolder(View itemView) {
super(itemView);
image=(ImageView)itemView.findViewById(R.id.image);
selectImage=(CheckBox) itemView.findViewById(R.id.ch);
}
}
}
Use an array to hold the state of the items
In the adapter use a Map or a SparseBooleanArray (which is similar to a Map, but is a key-value pair of int and boolean) to store the state of all the items in our list of items and then use the keys and values to compare when toggling the checked state.
In the Adapter create a SparseBooleanArray:
// sparse boolean array for checking the state of the items
private SparseBooleanArray itemStateArray = new SparseBooleanArray();
Then in the item click handler onClick() use the state of the items in the itemStateArray to check before toggling, here is an example
#Override
public void onClick(View v) {
int adapterPosition = getAdapterPosition();
if (!itemStateArray.get(adapterPosition, false)) {
mCheckedTextView.setChecked(true);
itemStateArray.put(adapterPosition, true);
} else {
mCheckedTextView.setChecked(false);
itemStateArray.put(adapterPosition, false);
}
}
Also, use sparse boolean array to set the checked state when the view is bound:
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.bind(position);
}
#Override
public int getItemCount() {
if (items == null) {
return 0;
}
return items.size();
}
void loadItems(List<Model> tournaments) {
this.items = tournaments;
notifyDataSetChanged();
}
class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
CheckedTextView mCheckedTextView;
ViewHolder(View itemView) {
super(itemView);
mCheckedTextView = (CheckedTextView) itemView.findViewById(R.id.checked_text_view);
itemView.setOnClickListener(this);
}
// use the sparse boolean array to check
void bind(int position) {
if (!itemStateArray.get(position, false)) {
mCheckedTextView.setChecked(false);}
else {
mCheckedTextView.setChecked(true);
}
}
}
and final adapter will be like this.
This will happened when use setOnCheckedChangeListener instead of that use setObClickListener and inside that just do this easy handle:
if (list.get(position).isCheck()) {
list.get(position).setCheck(false);
} else {
list.get(position).setCheck(true);
}
Note: in your list model add one boolean variable with name check and set getter and setter for that , in above case mine is setCheck and isCheck
This is a Kotlin Solution That Worked for Me
class SpecialtyFragmentRecyclerAdapter : RecyclerView.Adapter<SpecialtyFragmentRecyclerAdapter.SpecialtyViewHolder>(){
private var _specialtySet = mutableSetOf(
"Yoruba Attires",
"Hausa Attires",
"Senator",
"Embroidery",
"Africa Fashion",
"School Uniform",
"Military and Para-Military Uniforms",
"Igbo Attires",
"South-South Attires",
"Kaftans",
"Contemporary",
"Western Fashion",
"Caps"
).toSortedSet()
val specialtySet: Set<String> get() = _specialtySet
val savedSpecialtySet = mutableSetOf<String>().toSortedSet()
inner class SpecialtyViewHolder(
var itemBinding: SpecialtyFragmentRecyclerItemBinding
) :
RecyclerView.ViewHolder(itemBinding.root) {
fun bind(specialty: String) = with(itemBinding) {
specialtyFragmentYorubaAttiresCheckBox.text = specialty
specialtyFragmentYorubaAttiresCheckBox.isChecked = savedSpecialtySet.contains(specialty)
//AREA OF INTEREST
//Either Setting the CheckBox onCheckChangeListener to works
specialtyFragmentYorubaAttiresCheckBox.setOnCheckedChangeListener(null)
specialtyFragmentYorubaAttiresCheckBox.setOnCheckedChangeListener(
CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
if (buttonView.isPressed) { //OR this Also Works {Check if the Button is Pressed Before verifying the Checked State}
if (isChecked) {
savedSpecialtySet.add(specialty) //Perform Your Operation for Checked State
} else {
savedSpecialtySet.remove(specialty) //Perform Your Operation for unChecked State
}
}
}
)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SpecialtyViewHolder {
val viewBinding = SpecialtyFragmentRecyclerItemBinding
.inflate(LayoutInflater.from(parent.context), parent, false)
return SpecialtyViewHolder(viewBinding)
}
override fun onBindViewHolder(holder: SpecialtyViewHolder, position: Int) {
val specialty = _specialtySet.elementAt(position)
holder.bind(specialty)
}
override fun getItemCount(): Int {
return _specialtySet.size
}
fun populateList(list: MutableList<String>) {
savedSpecialtySet.addAll(list)
_specialtySet.addAll(list)
notifyDataSetChanged()
}
fun addNewSpecialty(specialty: String) {
_specialtySet.add(specialty.trim())
savedSpecialtySet.add(specialty.trim())
notifyDataSetChanged()
}
fun removeSpecialty(element: String) {
_specialtySet.remove(element)
savedSpecialtySet.remove(element)
notifyDataSetChanged()
}
}
I had the same problem in a RecyclerView list with switches, and solved it using #oguzhand answer, but with this code inside the checkedChangeListener:
if (buttonView.isPressed()) {
if (isChecked) {
group.setSelected(true);
} else {
group.setSelected(false);
}
} else {
if (isChecked) {
buttonView.setChecked(false);
} else {
buttonView.setChecked(true);
}
}
Where 'group' is the entity I want to select/deselect.
I've had the same issue. When I was clicking on item's - toggle buttons become checked in my RecyclerView. Toggle buttons appeared in every 10th item (for example if it was clicked in item with 0 index, items with 9, 18, 27 indexes were getting clicked too).
My code in onBindViewHolder was:
if (newsItems.get(position).getBookmark() == 1) {
holder.getToggleButtonBookmark().setChecked(true);
}
But then I added else statement:
/**
* Else statement prevents auto toggling.
*/
if (newsItems.get(position).getBookmark() == 1) {
holder.getToggleButtonBookmark().setChecked(true);
} else{
holder.getToggleButtonBookmark().setChecked(false);
}
And the problem was solved!
You need to separate onBindViewHolder(logic) interactions with CheckBox and user interactions with checkBox. I used OnCheckedChangeListener for user interactions (obviously) and ViewHolder.bind() for logic, that's why you need to set checked listener to null before setting up holder and after holder is ready - configure checked listener for user interactions.
boolean[] checkedStatus = new boolean[numberOfRows];
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {
final ViewHolderItem itemHolder = (ViewHolderItem) holder;
// holder.bind should not trigger onCheckedChanged, it should just update UI
itemHolder.checkBox.setOnCheckedChangeListener(null);
itemHolder.bind(position);
itemHolder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
checkedStatus[holder.getAdapterPosition()] = true;
performCheckedActions(); //your logic here
} else {
checkedStatus[holder.getAdapterPosition()] = false;
performUncheckedActions(); //your logic here
}
}
});
}
public void bind(int position) {
boolean checked = checkedStatus[position];
if (checked) {
checkBox.setChecked(false);
} else {
checkBox.setChecked(true);
}
}
I solved this problem by creating a static global array and using it in onBindViewHolder
RecyclerView.Adapter realization class:
In which I created all global variables/objects needed.
public class RVAdapter extends RecyclerView.Adapter<RVAdapter.PersonViewHolder> {
private Context context;
...
public static class PersonViewHolder extends RecyclerView.ViewHolder {
CardView cv;
TextView question, category;
TextView personAge;
ImageView upvote;
Button b1;
public static int k;
private int visibleThreshold = 5;
public static int i = 0;
static int check[]; //Static array
PersonViewHolder(View itemView, int i) {
super(itemView);
if(i == PersonViewHolder.k) {
b1 = (Button) itemView.findViewById(R.id.loadmore);
} else {
cv = (CardView) itemView.findViewById(R.id.cv);
question = (TextView) itemView.findViewById(R.id.question);
category = (TextView) itemView.findViewById(R.id.text_categ);
personAge = (TextView) itemView.findViewById(R.id.text1);
upvote = (ImageView) itemView.findViewById(R.id.upvote);
}
}
}
...
}
Here (in contructor of RVAdapter class) I gave size to the array equals to the size of items I'm going to display in the RecyclerView:
List<Person> persons;
RVAdapter(List<Person> persons){
this.persons = persons;
PersonViewHolder.check = new int[persons.size()];
PersonViewHolder.k = persons.size();
}
In onBindViewHolder I applied this concept on a button. When I click on a button - the background image of the button changes.
Object of button I used is names as "upvote", as "i" holds the position of each item in RecyclerView. I used it as an index of array which is working as a flag and which is keeping track of status of elements.
#Override
public void onBindViewHolder(final PersonViewHolder personViewHolder, final int i) {
if (i == PersonViewHolder.k) {
personViewHolder.b1.setText("load more");
} else {
personViewHolder.question.setText(persons.get(i).name);
personViewHolder.personAge.setText(persons.get(i).age);
if (personViewHolder.check[i] == 0) {
personViewHolder.upvote.setBackgroundResource(R.drawable.noupvote);
} else {
personViewHolder.upvote.setBackgroundResource(R.drawable.upvote);
}
personViewHolder.upvote.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (personViewHolder.check[i] == 0) {
personViewHolder.check[i] = 1;
personViewHolder.upvote.setBackgroundResource(R.drawable.upvote);
} else {
personViewHolder.check[i] = 0;
personViewHolder.upvote.setBackgroundResource(R.drawable.noupvote);
}
}
});
// personViewHolder.personPhoto.setImageResource(persons.get(i).photoId);
}
}
Okay there is a lot of answers here. But I will post my code and I will simply explain what I did... it maybe help juniors like me :D.
Objective:
We will create a list of RecyclerView that has CheckBox and RadioButton, something like this:
Model for list item with all needed data:
public class ModelClass {
private String time;
private boolean checked;
private boolean free;
private boolean paid;
public TherapistScheduleModel(String time, boolean checked, boolean free, boolean paid) {
this.time = time;
this.checked = checked;
this.free = free;
this.paid = paid;
}
public boolean isFree() {
return free;
}
public void setFree(boolean free) {
this.free = free;
}
public boolean isPaid() {
return paid;
}
public void setPaid(boolean paid) {
this.paid = paid;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
public boolean getChecked() {
return checked;
}
public void setChecked(boolean checked) {
this.checked= checked;
}
}
My RecyclerView.Adapter amazing realization:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private Context context;
private ListAllListeners listAllListeners;
private ArrayList<ModelClass> mDataList;
public MyAdapter(
Context context,
ArrayList<ModelClass> mDataList,
ListAllListeners listAllListeners
) {
this.mDataList = mDataList;
this.listAllListeners = listAllListeners;
this.context = context;
}
#NonNull
#Override
public MyViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View view = inflater.inflate(R.layout.single_view, parent, false);
return new MyViewHolder(view);
}
#Override
public int getItemCount() {
if (mDataList != null) {
return mDataList.size();
} else {
return 0;
}
}
#Override
public void onBindViewHolder(#NonNull final MyViewHolder holder, final int position) {
// important to:
// setOnCheckedChangeListener to 'null'
holder.checkBoxTime.setOnCheckedChangeListener(null);
holder.freeRB.setOnCheckedChangeListener(null);
holder.paidRB.setOnCheckedChangeListener(null);
// Check Box
holder.checkBoxTime.setText(mDataList.get(holder.getAdapterPosition()).getTime());
// Here we check if the item is checked or not from the model.
if(mDataList.get(holder.getAdapterPosition()).getChecked()) {
holder.checkBoxTime.setChecked(true);
} else {
holder.checkBoxTime.setChecked(false);
}
holder.checkBoxTime.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
if (b) {
mDataList.get(holder.getAdapterPosition()).setChecked(true);
listAllListeners.onItemCheck(holder.checkBoxTime.getText().toString(), holder.getAdapterPosition());
} else {
mDataList.get(holder.getAdapterPosition()).setChecked(false);
listAllListeners.onItemUncheck(holder.checkBoxTime.getText().toString(), holder.getAdapterPosition());
}
}
});
// Radio Buttons
if(mDataList.get(holder.getAdapterPosition()).isFree()) {
holder.freeRB.setChecked(true);
} else {
holder.freeRB.setChecked(false);
}
holder.freeRB.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
if (b) {
mDataList.get(holder.getAdapterPosition()).setFree(true);
listAllListeners.onFreeCheck(holder.freeRB.getText().toString(), holder.getAdapterPosition());
} else {
mDataList.get(holder.getAdapterPosition()).setFree(false);
listAllListeners.onFreeUncheck(holder.freeRB.getText().toString(), holder.getAdapterPosition());
}
}
});
// And so on to paidRB
}
/**
* Here is a list of clicked listeners to use them as you want ;).
* You can get a list of checked or unchecked of all.
*/
public interface ListAllListeners {
void onItemCheck(String checkBoxName, int position);
void onItemUncheck(String checkBoxName, int position);
void onFreeCheck(String name, int pos);
void onFreeUncheck(String name, int pos);
void onPaidCheck(String name, int pos);
void onPaidUncheck(String name, int pos);
}
class MyViewHolder extends RecyclerView.ViewHolder {
CheckBox checkBoxTime;
RadioButton freeRB, paidRB;
MyViewHolder(View itemView) {
super(itemView);
checkBoxTime = itemView.findViewById(R.id.timeCheckBox);
freeRB = itemView.findViewById(R.id.freeRadioBtn);
paidRB = itemView.findViewById(R.id.paidRadioBtn);
}
}
}
In Activity you get them something like this:
myAdapter = new MyAdapter(this, mDataList, new MyAdapter.ListAllListeners() {
#Override
public void onItemCheck(String checkBoxName, int position) {
Toast.makeText(getActivity(), "" + checkBoxName + " " + position, Toast.LENGTH_SHORT).show();
}
#Override
public void onItemUncheck(String checkBoxName, int position) {
Toast.makeText(getActivity(), "" + checkBoxName + " " + position, Toast.LENGTH_SHORT).show();
}
#Override
public void onFreeCheck(String name, int position) {
Toast.makeText(getActivity(), "" + name + " " + position, Toast.LENGTH_SHORT).show();
}
#Override
public void onFreeUncheck(String name, int position) {
Toast.makeText(getActivity(), "" + name + " " + position, Toast.LENGTH_SHORT).show();
}
#Override
public void onPaidCheck(String name, int position) {
Toast.makeText(getActivity(), "" + name + " " + position, Toast.LENGTH_SHORT).show();
}
#Override
public void onPaidUncheck(String name, int position) {
Toast.makeText(getActivity(), "" + name + " " + position, Toast.LENGTH_SHORT).show();
}
});
this is due to again and again creating view ,best option is clear cache before setting adapter
recyclerview.setItemViewCacheSize(your array.size());
In onBindViewHolder for views (checkbox, radio, switch, ...) you should setOnCheckedChangeListener(null) before and after new creation. For example:
public void onBindViewHolder(#NonNull ViewHolder holder,
int position) {
...
holder.switchCompat.setOnCheckedChangeListener(null);
...
holder.switchCompat.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton compoundButton,
boolean b) {
// TODO: 10/23/2022 do something
}
});
}
Solution is while CheckBox is checked. Need to store this separate list, and use that list to populate CheckBox in RecyclerView.
You can refer this link.
Complete example:
public class ChildAddressAdapter extends RecyclerView.Adapter<ChildAddressAdapter.CartViewHolder> {
private Activity context;
private List<AddressDetail> addressDetailList;
private int selectedPosition = -1;
public ChildAddressAdapter(Activity context, List<AddressDetail> addressDetailList) {
this.context = context;
this.addressDetailList = addressDetailList;
}
#NonNull
#Override
public CartViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(context);
View myView = inflater.inflate(R.layout.address_layout, parent, false);
return new CartViewHolder(myView);
}
#Override
public void onBindViewHolder(#NonNull CartViewHolder holder, int position) {
holder.adress_checkbox.setOnClickListener(view -> {
selectedPosition = holder.getAdapterPosition();
notifyDataSetChanged();
});
if (selectedPosition==position){
holder.adress_checkbox.setChecked(true);
}
else {
holder.adress_checkbox.setChecked(false);
}
}
#Override
public int getItemCount() {
return addressDetailList.size();
}
class CartViewHolder extends RecyclerView.ViewHolder {
TextView address_text,address_tag;
CheckBox adress_checkbox;
CartViewHolder(View itemView) {
super(itemView);
address_text = itemView.findViewById(R.id.address_text);
address_tag = itemView.findViewById(R.id.address_tag);
adress_checkbox = itemView.findViewById(R.id.adress_checkbox);
}
}
}
public class TagYourDiseaseAdapter extends RecyclerView.Adapter<TagYourDiseaseAdapter.OrderHistoryViewHolder> {
private ReCyclerViewItemClickListener mRecyclerViewItemClickListener;
private Context mContext;
List<Datum> deviceList = Collections.emptyList();
/**
* Initialize the values
*
* #param context : context reference
* #param devices : data
*/
public TagYourDiseaseAdapter(Context context, List<Datum> devices,
ReCyclerViewItemClickListener mreCyclerViewItemClickListener) {
this.mContext = context;
this.deviceList = devices;
this.mRecyclerViewItemClickListener = mreCyclerViewItemClickListener;
}
/**
* #param parent : parent ViewPgroup
* #param viewType : viewType
* #return ViewHolder
* <p>
* Inflate the Views
* Create the each views and Hold for Reuse
*/
#Override
public TagYourDiseaseAdapter.OrderHistoryViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_tag_disease, parent, false);
TagYourDiseaseAdapter.OrderHistoryViewHolder myViewHolder = new TagYourDiseaseAdapter.OrderHistoryViewHolder(view);
return myViewHolder;
}
/**
* #param holder : view Holder
* #param position : position of each Row set the values to the views
*/
#Override
public void onBindViewHolder(final TagYourDiseaseAdapter.OrderHistoryViewHolder holder, final int position) {
Picasso.with(mContext).load(deviceList.get(position).getIconUrl()).into(holder.document);
holder.name.setText(deviceList.get(position).getDiseaseName());
holder.radioButton.setOnCheckedChangeListener(null);
holder.radioButton.setChecked(deviceList.get(position).isChecked());
//if true, your checkbox will be selected, else unselected
//holder.radioButton.setChecked(objIncome.isSelected());
holder.radioButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
deviceList.get(position).setChecked(isChecked);
}
});
}
#Override
public int getItemCount() {
return deviceList.size();
}
/**
* Create The view First Time and hold for reuse
* View Holder for Create and Hold the view for ReUse the views instead of create again
* Initialize the views
*/
public class OrderHistoryViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
ImageView document;
TextView name;
CheckBox radioButton;
public OrderHistoryViewHolder(View itemView) {
super(itemView);
document = itemView.findViewById(R.id.img_tag);
name = itemView.findViewById(R.id.text_tag_name);
radioButton = itemView.findViewById(R.id.rdBtn_tag_disease);
radioButton.setOnClickListener(this);
//this.setIsRecyclable(false);
}
#Override
public void onClick(View view) {
mRecyclerViewItemClickListener.onItemClickListener(this.getAdapterPosition(), view);
}
}
}
If it is not late; this is actually RecyclerView general problem. You can put your RecyclerView into a NestedScrollView and then add one line code to your adapter. All is done.
In your activity or fragment;
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
And in your activity where you set adapter add this:
ViewCompat.setNestedScrollingEnabled(recyclerView, false);
LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
// your adapter code...
recyclerView.setAdapter(textSearchAdapter);
I faced the similar issue while using checkbox inside recycler view. After some detail analysis I got the root cause. let's look at the code once
In onBindViewHolder the line "holder.cbSelect.setChecked(yourList.isSelected());"
will always execute.
If we scroll up or scroll down the page, the onBindViewHolder will get called. As soon as onBindViewHolder will get called "holder.cbSelect.setChecked(yourList.isSelected());" will get tiggered and as a result
"holder.cbSelect.setOnCheckedChangeListener" will also get called and it will change the checkbox state, even if you have not changed the checkbox state. The reason is simple that it found checkbox state is changed from your updated list (yourList.isSelected()) which you select or dis-select the check box .
Now as a solution in override method of "public void onCheckedChanged(CompoundButton buttonView, boolean isChecked)" method we need to add one condition that is
holder.cbSelect.setOnCheckedChangeListener(new OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (buttonView.isPressed()) {
//Check box state changed by user
//update your list based on checkbox value
// yourList.setSelected(isChecked);
}
}
});
What worked for me is to nullify the listeners on the viewHolder when the view is going to be recycled (onViewRecycled):
override fun onViewRecycled(holder: AttendeeViewHolder) {
super.onViewRecycled(holder)
holder.itemView.hasArrived.setOnCheckedChangeListener(null);
holder.itemView.edit.setOnClickListener { null }
}
Adding setItemViewCacheSize(int size) to RecyclerView and passing size of list solved my problem.
My code:
mrecyclerview.setItemViewCacheSize(mOrderList.size());
mBinding.mrecyclerview.setAdapter(mAdapter);
Source: link

Recycler View not calling onBindviewHolder

OnBindViewHolder isn't called when I try to select an element inside the recycler view.
I'm using a horizontal layout with all the elements not showing initially (4 out of 7 elements are showing and when user motions to right the 3 elements alternate).
Usually, when the user clicks an element OnBindViewHolder is supposed to fire but it's not happening for me. The only time it fires is on initialization. Since it doesn't fire I can't click any of the elements inside the recycler view. Maybe it might have to do with my layouts? I'm not sure
MyAdapter
public MyAdapter(){
this.setHasStableIds(true);
}
//Set Keys
public void setSelectionTracker(SelectionTracker<Long> selectionTracker) {
this.mSelectionTracker = selectionTracker;
}
public static class MyViewHolder extends RecyclerView.ViewHolder{
public TextView dayView, numberView;
public View view;
ScheduleDetails scheduleDetails = new ScheduleDetails();
public MyViewHolder(View itemView){
super(itemView);
view = itemView;
dayView = itemView.findViewById(R.id.day);
numberView = itemView.findViewById(R.id.day_number);
}
void bind(int position, String dayInit, String numberInit, Boolean isSelected){
scheduleDetails.position = position;
System.out.println("Hit2: " + scheduleDetails.position);
dayView.setText(dayInit);
numberView.setText(numberInit);
view.setSelected(isSelected);
}
public ItemDetailsLookup.ItemDetails<Long> getItemDetails(#NonNull MotionEvent motionEvent){
return scheduleDetails;
}
}
static class ScheduleDetails extends ItemDetailsLookup.ItemDetails<Long>{
int position;
Long identifier;
#Override
public int getPosition() {
return position;
}
#Nullable
#Override
public Long getSelectionKey() {
return identifier;
}
#Override
public boolean inSelectionHotspot(#NonNull MotionEvent e){
return true;
}
}
public MyAdapter(String[] day, String[] number){
days = day;
numbers = number;
}
#Override
public MyAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.recycler_layout, parent, false);
MyViewHolder vh = new MyViewHolder(view);
return vh;
}
#Override
public void onBindViewHolder(MyViewHolder holder, int position) {
String dayInit = days[position];
String numberInit = numbers[position];
Long positions = (long) position;
System.out.println("Position: " + position);
boolean isSelected = false;
if(mSelectionTracker != null){
if(mSelectionTracker.isSelected(positions)){
isSelected = true;
}
holder.bind(position, dayInit, numberInit, isSelected);
}
}
// Return the size of your dataset (invoked by the layout manager)
#Override
public int getItemCount() {
return days.length;
}
#Override
public long getItemId(int position) {
return position;
}
SOLVED
#Override
public void onBindViewHolder(MyViewHolder holder, int position) {
String dayInit = days[position];
String numberInit = numbers[position];
Long id = getItemId(position);
boolean isSelected = false;
if(mSelectionTracker != null) {
if (mSelectionTracker.isSelected(id)) {
isSelected = true;
}
}
System.out.println("Here: " + isSelected);
holder.bind(position, id, dayInit, numberInit, isSelected);
}
#Override
public long getItemId(int position) {
return position;
}
I retrieved position from getItemId and put that into my id variable (I used setHasStableIds). Click Listener isn't needed for this implementation. This is for the androidx recyclerview-selection library
Lastly I declared these in MyViewHolder:
scheduleDetails.position = position;
scheduleDetails.identifier = key;

Where to manipulate the data in Recyclerview Adapter (Android)

My datetime is currently stored as UNIX time stamp. I want to display it as h:mm a in my Recyclerview.
Where should I convert the UNIX time stamp into normal time in the RecyclerView Adapter/Viewholder (in terms of the best performance)?
Should I do it in the getItemViewType(int position) of the RecyclerView.Adapter, or the onBindViewHolder or the bind function of the ViewHolder class?
Edit: My code
public class ChatListAdapter extends RecyclerView.Adapter {
private final LayoutInflater mInflater;
private List<Chat> mChats;
private final String ownerMe = "OWNER_ME";
private static final int VIEW_TYPE_MESSAGE_ME = 1;
private static final int VIEW_TYPE_MESSAGE_ME_CORNER = 2;
private static final int VIEW_TYPE_MESSAGE_BF = 3;
private static final int VIEW_TYPE_MESSAGE_BF_CORNER = 4;
ChatListAdapter(Context context) {mInflater = LayoutInflater.from(context);}
#Override
public int getItemViewType(int position) {
Chat chat = mChats.get(position);
if(chat.getUser().equals(ownerMe)) {
if(position == mChats.size()-1) {
return VIEW_TYPE_MESSAGE_ME_CORNER;
}
if(chat.getUser().equals(mChats.get(position+1).getUser())) {
return VIEW_TYPE_MESSAGE_ME;
} else {
return VIEW_TYPE_MESSAGE_ME_CORNER;
}
} else {
if(position == mChats.size()-1) {
return VIEW_TYPE_MESSAGE_BF_CORNER;
}
if(chat.getUser().equals(mChats.get(position+1).getUser())) {
return VIEW_TYPE_MESSAGE_BF;
} else {
return VIEW_TYPE_MESSAGE_BF_CORNER;
}
}
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view;
if(viewType == VIEW_TYPE_MESSAGE_ME || viewType == VIEW_TYPE_MESSAGE_ME_CORNER) {
view = mInflater.inflate(R.layout.recyclerview_item_right, parent, false);
return new MeMessageHolder(view);
} else if (viewType == VIEW_TYPE_MESSAGE_BF || viewType == VIEW_TYPE_MESSAGE_BF_CORNER) {
view = mInflater.inflate(R.layout.recyclerview_item_left, parent, false);
return new BfMessageHolder(view);
}
return null;
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (mChats != null) {
Chat current = mChats.get(position);
long unixTime= current.getUnixTime();
Date time = new java.util.Date(unixTime*1000L);
SimpleDateFormat sdf = new java.text.SimpleDateFormat("h:mm a");
String formattedTime = sdf.format(time);
switch (holder.getItemViewType()) {
case VIEW_TYPE_MESSAGE_ME:
((MeMessageHolder) holder).bind(current, formattedTime, false);
break;
case VIEW_TYPE_MESSAGE_ME_CORNER:
((MeMessageHolder) holder).bind(current, formattedTime, true);
break;
case VIEW_TYPE_MESSAGE_BF:
((BfMessageHolder) holder).bind(current, formattedTime, false);
break;
case VIEW_TYPE_MESSAGE_BF_CORNER:
((BfMessageHolder) holder).bind(current, formattedTime, true);
break;
}
}
}
class MeMessageHolder extends RecyclerView.ViewHolder {
private final TextView chatItemView;
private final ImageView cornerRightIImageView;
private final ConstraintLayout constraintLayout;
private final TextView timeItemView;
private MeMessageHolder(View itemView) {
super(itemView);
chatItemView = itemView.findViewById(R.id.textView);
cornerRightIImageView = itemView.findViewById(R.id.corner_view_right);
constraintLayout = itemView.findViewById(R.id.chat_bubble_id);
timeItemView = itemView.findViewById(R.id.text_message_time);
}
void bind(Chat chat, String formattedTime, boolean isCorner) {
chatItemView.setText(chat.getMessage());
timeItemView.setText(formattedTime);
if(isCorner) {
constraintLayout.setBackgroundResource(R.drawable.chat_bubble_v2);
} else {
cornerRightIImageView.setVisibility(View.INVISIBLE);
}
}
}
class BfMessageHolder extends RecyclerView.ViewHolder {
private final TextView chatItemView;
private final ImageView cornerLeftImageView;
private final ConstraintLayout constraintLayout;
private final TextView timeItemView;
private BfMessageHolder(View itemView) {
super(itemView);
chatItemView = itemView.findViewById(R.id.textView);
cornerLeftImageView = itemView.findViewById(R.id.corner_view_left);
constraintLayout = itemView.findViewById(R.id.chat_bubble_id);
timeItemView = itemView.findViewById(R.id.text_message_time);
}
void bind(Chat chat, String formattedTime, boolean isCorner) {
chatItemView.setText(chat.getMessage());
timeItemView.setText(formattedTime);
if(isCorner) {
constraintLayout.setBackgroundResource(R.drawable.chat_bubble_v3);
} else {
cornerLeftImageView.setVisibility(View.INVISIBLE);
}
}
}
void setChats(List<Chat> chats) {
mChats = chats;
notifyDataSetChanged();
}
#Override
public int getItemCount() {
if(mChats!=null)
return mChats.size();
else return 0;
}
}
This is method correct? I formatted the date in the onBindViewHoldermethod
It depends whether you want to display different dates on different items of the recyclerview, or the same date on all items of the recyclerview.
If you want to show same date to all items, better to do it outside of the adapter and then pass the parsed date to the recyclerview adapter.
Or, if you want to show different dates on each item, you should do it inside onBindViewHolder as it has access to the item position.
Remember, getItemViewType is used for getting a view type out of the available ones. This is used in case you are inflating multiple views. Think of a chatapp where view1 will display message on the left, and view2 will display message on the right; all within the same recyclerview.
The onBindViewHolder method simply performs a generic binding task. Binds what : the item of the inflated view and the data.
It seems like business logic. So, i recommend to move you UNIX time stamp convertation in Model for example.
class Chat {
private Long unixTime;
// another code
public Long getUnixTime() {
return unixTime;
}
public String convertedUnixTimeToString(String format) {
// Also need to add some format validation
if(format == null) {
// do some action, like trowing exception, or setting default value in format
}
Date time = new java.util.Date(unixTime*1000L);
SimpleDateFormat sdf = new java.text.SimpleDateFormat(format);
return sdf.format(time);
}
}
I recommend you to use JodaTime for date&time formatting. Very useful thing.
And then, just modify your code
public class ChatListAdapter extends RecyclerView.Adapter {
private final LayoutInflater mInflater;
private List<Chat> mChats;
private final String ownerMe = "OWNER_ME";
private static final int VIEW_TYPE_MESSAGE_ME = 1;
private static final int VIEW_TYPE_MESSAGE_ME_CORNER = 2;
private static final int VIEW_TYPE_MESSAGE_BF = 3;
private static final int VIEW_TYPE_MESSAGE_BF_CORNER = 4;
ChatListAdapter(Context context) {mInflater = LayoutInflater.from(context);}
#Override
public int getItemViewType(int position) {
Chat chat = mChats.get(position);
if(chat.getUser().equals(ownerMe)) {
if(position == mChats.size()-1) {
return VIEW_TYPE_MESSAGE_ME_CORNER;
}
if(chat.getUser().equals(mChats.get(position+1).getUser())) {
return VIEW_TYPE_MESSAGE_ME;
} else {
return VIEW_TYPE_MESSAGE_ME_CORNER;
}
} else {
if(position == mChats.size()-1) {
return VIEW_TYPE_MESSAGE_BF_CORNER;
}
if(chat.getUser().equals(mChats.get(position+1).getUser())) {
return VIEW_TYPE_MESSAGE_BF;
} else {
return VIEW_TYPE_MESSAGE_BF_CORNER;
}
}
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view;
if(viewType == VIEW_TYPE_MESSAGE_ME || viewType == VIEW_TYPE_MESSAGE_ME_CORNER) {
view = mInflater.inflate(R.layout.recyclerview_item_right, parent, false);
return new MeMessageHolder(view);
} else if (viewType == VIEW_TYPE_MESSAGE_BF || viewType == VIEW_TYPE_MESSAGE_BF_CORNER) {
view = mInflater.inflate(R.layout.recyclerview_item_left, parent, false);
return new BfMessageHolder(view);
}
return null;
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (mChats != null) {
Chat current = mChats.get(position);
switch (holder.getItemViewType()) {
case VIEW_TYPE_MESSAGE_ME:
((MeMessageHolder) holder).bind(current, false);
break;
case VIEW_TYPE_MESSAGE_ME_CORNER:
((MeMessageHolder) holder).bind(current, true);
break;
case VIEW_TYPE_MESSAGE_BF:
((BfMessageHolder) holder).bind(current, false);
break;
case VIEW_TYPE_MESSAGE_BF_CORNER:
((BfMessageHolder) holder).bind(current, true);
break;
}
}
}
class MeMessageHolder extends RecyclerView.ViewHolder {
private final TextView chatItemView;
private final ImageView cornerRightIImageView;
private final ConstraintLayout constraintLayout;
private final TextView timeItemView;
private MeMessageHolder(View itemView) {
super(itemView);
chatItemView = itemView.findViewById(R.id.textView);
cornerRightIImageView = itemView.findViewById(R.id.corner_view_right);
constraintLayout = itemView.findViewById(R.id.chat_bubble_id);
timeItemView = itemView.findViewById(R.id.text_message_time);
}
void bind(Chat chat, boolean isCorner) {
chatItemView.setText(chat.getMessage());
timeItemView.setText(chat.convertedUnixTimeToString("h:mm a"));
if(isCorner) {
constraintLayout.setBackgroundResource(R.drawable.chat_bubble_v2);
} else {
cornerRightIImageView.setVisibility(View.INVISIBLE);
}
}
}
class BfMessageHolder extends RecyclerView.ViewHolder {
private final TextView chatItemView;
private final ImageView cornerLeftImageView;
private final ConstraintLayout constraintLayout;
private final TextView timeItemView;
private BfMessageHolder(View itemView) {
super(itemView);
chatItemView = itemView.findViewById(R.id.textView);
cornerLeftImageView = itemView.findViewById(R.id.corner_view_left);
constraintLayout = itemView.findViewById(R.id.chat_bubble_id);
timeItemView = itemView.findViewById(R.id.text_message_time);
}
void bind(Chat chat, boolean isCorner) {
chatItemView.setText(chat.getMessage());
timeItemView.setText(chat.convertedUnixTimeToString("h:mm a"));
if(isCorner) {
constraintLayout.setBackgroundResource(R.drawable.chat_bubble_v3);
} else {
cornerLeftImageView.setVisibility(View.INVISIBLE);
}
}
}
void setChats(List<Chat> chats) {
mChats = chats;
notifyDataSetChanged();
}
#Override
public int getItemCount() {
if(mChats!=null)
return mChats.size();
else return 0;
}
}
You should be updating the UI changes in onBindViewHolder method. You can call bind method of ViewHolder in onBindViewHolder.
Example:
public class SampleAdapter extends RecyclerView.Adapter<SampleAdapter.ViewHolder> {
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.sample_view, viewGroup, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull ViewHolder viewHolder, int i) {
viewHolder.bind(i);
}
public class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(#NonNull View itemView) {
super(itemView);
}
void bind(int position) {
// Do your data updates here.
}
}
}
Just Use SimpleDateFormat with yyyy-MM-dd pattern .
Apply SimpleDateFormat.format(millis) in onBindViewHolder method of RecyclerView.
You need to convert it to milliseconds by multiplying the timestamp by 1000:
java.util.Date dateTime=new java.util.Date((long)timeStamp*1000);
then first you need to convert UNIX timestamp to datetime format
final long unixTime = 1372339860;
final String formattedDtm = Instant.ofEpochSecond(unixTime)
.atZone(ZoneId.of("GMT-4"))
.format(formatter);
System.out.println(formattedDtm); // => '2013-06-27 09:31:00'
then you want to store this data to field value of RecyclerView
then you can format it from this time format like h:mm

RecyclerView is duplicating the first three items on a list

Im fairly new to android and i am trying to use a RecyclerView to display content hosted on firebase, but when it comes up the first three items are duplicated.
I have tried a few solutions around but none seem to work, any help would be great!
DiscountRecyclerAdapter.java
public class DiscountRecyclerAdapter extends RecyclerView.Adapter<com.cianod.comharapp.DiscountRecyclerAdapter.ViewHolder> {
public List<Discounts> discountsList;
public Context context;
private ImageView discountImageView;
public DiscountRecyclerAdapter(List<Discounts> discountsList){
this.discountsList = discountsList;
}
#Override
public com.cianod.comharapp.DiscountRecyclerAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.discount_list_item, parent, false);
context = parent.getContext();
return new com.cianod.comharapp.DiscountRecyclerAdapter.ViewHolder(view);
}
#Override
public void onBindViewHolder(final com.cianod.comharapp.DiscountRecyclerAdapter.ViewHolder holder, int position) {
holder.setIsRecyclable(false);
String discountName = discountsList.get(position).getDiscount_name();
holder.setDiscount_Name(discountName);
String discountDescription = discountsList.get(position).getDiscount_description();
holder.setDiscount_Description(discountDescription);
String discountValue = discountsList.get(position).getDiscount_value();
holder.setDiscount_Value(discountValue);
String image_url = discountsList.get(position).getDiscount_image();
String thumbUri = discountsList.get(position).getDiscount_image();
holder.setDiscount_Image(image_url, thumbUri);
}
#Override
public int getItemCount() {
if(discountsList != null) {
return discountsList.size();
} else {
return 0;
}
}
public class ViewHolder extends RecyclerView.ViewHolder {
private View mView;
private TextView discount_name;
public ViewHolder(View itemView) {
super(itemView);
mView = itemView;
}
public void setDiscount_Name(String message){
discount_name = mView.findViewById(R.id.discount_name);
discount_name.setText(message);
}
public void setDiscount_Description(String message){
discount_name = mView.findViewById(R.id.discount_description);
discount_name.setText(message);
}
public void setDiscount_Value(String message){
discount_name = mView.findViewById(R.id.discount_value);
discount_name.setText(message);
}
public void setDiscount_Image(String downloadUri, String thumbUri){
discountImageView = mView.findViewById(R.id.discount_image);
RequestOptions requestOptions = new RequestOptions();
requestOptions.placeholder(R.drawable.image_placeholder);
Glide.with(context).applyDefaultRequestOptions(requestOptions).load(downloadUri).thumbnail(
Glide.with(context).load(thumbUri)
).into(discountImageView);
}
}
}
I believe the problem is coming from the discount adapter but I cant say for sure. The RecyclerView is displayed on a fragment if that could be influencing it I can attach other pieces if they are necessary.
There is nothing wrong with your code.So maybe you should try to override those two methods in your adapter class
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getItemViewType(int position) {
return position;
}

In Recycle view how to Highlight always one adapter item with the click and without click

Here I clicked on the item to change item background and color. I've stored the clicked item value in the database and change the layout color and text color and recreating the adapter and showing the list again while refreshing.
But layout colors not changed when I get its position. Please show the right path to handle the set of background item color always.
public class LoadVehicleTypeAdapter extends RecyclerView.Adapter<LoadVehicleTypeAdapter.CarTypesHolder> {
private List<TaxiTypeResponse.Message> CarTypesModelsList;
private Context mContext;
VehicleTypeView vehicleTypeView;
int I = -1;
int idd = 0;
int II = 0;
Activity activity;
GoogleMap map;
List<VehicleClick> list;
private SparseBooleanArray selectedItems;
public class CarTypesHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public CustomTextView mCarType;
public CircleImageView mCarTypeImage;
LinearLayout llRoot;
CardView cardView;
setOnitemclick listener;
public void setOnItemClickListner(setOnitemclick listener) {
this.listener = listener;
}
public CarTypesHolder(View view) {
super(view);
mCarType = (CustomTextView) view.findViewById(R.id.frag_cartypes_inflated_name);
mCarTypeImage = (CircleImageView) view.findViewById(R.id.frag_cartype_inflated_frameImage);
llRoot = (LinearLayout) view.findViewById(R.id.root1);
selectedItems = new SparseBooleanArray();
view.setOnClickListener(this);
}
#Override
public void onClick(View v) {
listener.ImageClick(v, getAdapterPosition());
}
}
public LoadVehicleTypeAdapter(Context context, List<TaxiTypeResponse.Message> CarTypesModelsList, VehicleTypeView vehicleTypeView, Activity activity, GoogleMap map, List<VehicleClick> lists) {
this.CarTypesModelsList = CarTypesModelsList;
mContext = context;
this.vehicleTypeView = vehicleTypeView;
this.activity = activity;
this.map = map;
this.list = lists;
}
#Override
public CarTypesHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.frag_cartype_inflated_view, parent, false);
return new CarTypesHolder(itemView);
}
#SuppressLint("ResourceType")
#Override
public void onBindViewHolder(final CarTypesHolder holder, int position) {
if (list.size() != 0) {
II = Integer.parseInt(list.get(0).RideId);
//setSelection(Integer.parseInt(list.get(0).RideId));
}
if (II == position) {
holder.llRoot.setBackgroundColor(Color.parseColor("#999999"));
holder.mCarType.setTextColor(Color.parseColor("#FFFFFF"));
} else {
holder.llRoot.setBackgroundColor(Color.parseColor("#f3f3f3"));
holder.mCarType.setTextColor(Color.parseColor("#2196F3"));
}
SharedPreferences sharedPreferences = activity.getSharedPreferences("mSelected", Context.MODE_PRIVATE);
TaxiTypeResponse.Message carTypesModel = CarTypesModelsList.get(position);
holder.mCarType.setText(carTypesModel.getName());
holder.mCarTypeImage.setBackgroundResource(R.drawable.wait);
int color = Color.parseColor(PreferenceHandler.readString(mContext, PreferenceHandler.SECONDRY_COLOR, "#006fb6"));
holder.mCarType.setTextColor(color);
holder.setOnItemClickListner(new setOnitemclick() {
#Override
public void ImageClick(View v, int position1) {
I = position1;
notifyDataSetChanged();
try {
if (list.size() != 0) {
VehicleTypeFragment.myAppRoomDataBase.userDao().delete();
list.clear();
}
VehicleClick vehicleClick = new VehicleClick();
vehicleClick.setRideId(String.valueOf(position1));
VehicleTypeFragment.myAppRoomDataBase.userDao().insert(vehicleClick);
list.add(vehicleClick);
} catch (Exception e) {
}
}
});
if (I == position) {
holder.llRoot.setBackgroundColor(Color.parseColor("#999999"));
holder.mCarType.setTextColor(Color.parseColor("#ffffff"));
Animation bounce = AnimationUtils.loadAnimation(mContext, R.anim.bounce);
holder.llRoot.startAnimation(bounce);
} else {
holder.llRoot.setBackgroundColor(Color.parseColor("#f3f3f3"));
holder.mCarType.setTextColor(Color.parseColor("#2196F3"));
}
Picasso.with(mContext).load(carTypesModel.getImagePath()).into(holder.mCarTypeImage);
}
#Override
public long getItemId(int position) {
return CarTypesModelsList.get(position).getID();
}
#Override
public int getItemCount() {
return CarTypesModelsList.size();
}
public void setSelection(int position) {
II = position;
//notifyDataSetChanged();
}
public interface setOnitemclick {
void ImageClick(View view, int position);
}
#Override
public int getItemViewType(int position) {
return position;
}
}
I am not sure what did you mean by refreshing your list. I am guessing that you are recreating the adapter and showing the list again while you are refreshing. Hence the value of I is initialized with -1 each time you are creating the adapter.
You need to do the initialization as follows. Please consider the following is a pseudo code and you need to implement this of your own.
// While declaring your I
// int I = -1;
int I = getTheStoredValueFromDatabase(); // If there is no entry in database, getTheStoredValueFromDatabase function will return -1
I hope you get the idea. You might consider doing the same for other stored values.
for keep track record you need to add Boolean variable in TaxiTypeResponse.Message boolean isClick=false; and toggle this in
holder.setOnItemClickListner(new setOnitemclick() {
#Override
public void ImageClick(View v, int position) {
CarTypesModelsList.get(position).isClick=!CarTypesModelsList.get(position).isClick;
notifyDataSetChanged();
}
}
and modify below code as follow
if (CarTypesModelsList.get(position).isClick) {
holder.llRoot.setBackgroundColor(Color.parseColor("#999999"));
holder.mCarType.setTextColor(Color.parseColor("#ffffff"));
Animation bounce = AnimationUtils.loadAnimation(mContext, R.anim.bounce);
holder.llRoot.startAnimation(bounce);
}
else{
holder.llRoot.setBackgroundColor(Color.parseColor("#f3f3f3"));
holder.mCarType.setTextColor(Color.parseColor("#2196F3"));
}
Note: onBindViewHolder() is not a place to implement the click
listeners, but I am just providing you the logic for how to implement
single selection in recyclerView.
Now lets jump to the solution,
simply follow the below tutorial and change the variable, fields, and background according to your need, you have to implement the below method in onBindViewHolder() method of RecyclerView
First, initialize the lastClickedPosition and isclicked
int lastPositionClicked = -1;
boolean isClicked = false;
#Override
public void onBindViewHolder(#NonNull final MyViewHolder holder, final int position) {
holder.YOUR_VIEW.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// store the position which you have just clicked and you will change the background of this clicked view
lastPositionClicked = position;
isClicked = true;
// need to refresh the adapter
SlabAdapter.this.notifyDataSetChanged();
}
});
// only change the background of last clicked item
if (isClicked && position == lastPositionClicked) {
// change clicked view background color
} else {
// otherwise set the default color for all positions
}
}
let me know if this works.
on BindViewHolder method you'll use this code and set I=0 on globally
#SuppressLint("ResourceType")
#Override
public void onBindViewHolder(final CarTypesHolder holder, int position) {
SharedPreferences sharedPreferences = activity.getSharedPreferences("mSelected", Context.MODE_PRIVATE);
TaxiTypeResponse.Message carTypesModel = CarTypesModelsList.get(position);
holder.mCarType.setText(carTypesModel.getName());
holder.mCarTypeImage.setBackgroundResource(R.drawable.wait);
int color = Color.parseColor(PreferenceHandler.readString(mContext, PreferenceHandler.SECONDRY_COLOR, "#006fb6"));
holder.mCarType.setTextColor(color);
holder.setOnItemClickListner(new setOnitemclick() {
#Override
public void ImageClick(View v, int position1) {
I = position1;
notifyDataSetChanged();
try {
if (list.size() != 0) {
VehicleTypeFragment.myAppRoomDataBase.userDao().delete();
list.clear();
}
VehicleClick vehicleClick = new VehicleClick();
vehicleClick.setRideId(String.valueOf(position1));
VehicleTypeFragment.myAppRoomDataBase.userDao().insert(vehicleClick);
list.add(vehicleClick);
} catch (Exception e) {
}
}
});
if (I == position) {
holder.llRoot.setBackgroundColor(Color.parseColor("#999999"));
holder.mCarType.setTextColor(Color.parseColor("#ffffff"));
Animation bounce = AnimationUtils.loadAnimation(mContext, R.anim.bounce);
holder.llRoot.startAnimation(bounce);
} else {
holder.llRoot.setBackgroundColor(Color.parseColor("#f3f3f3"));
holder.mCarType.setTextColor(Color.parseColor("#2196F3"));
}
Picasso.with(mContext).load(carTypesModel.getImagePath()).into(holder.mCarTypeImage);
}

Categories

Resources