2

I am using a RecyclerView to display a list of items, but it refuses to display nicely despite my most sincere efforts. The items repeat after the first 14 in portrait and the first 10 in landscape, with the 15th/11th element scrolling its value with the user scroll. On top of that, scrolling will scramble data when repeatedly scrolling up/down. This link shows this behavior on a list of items numbered 1-28.

I have tried overriding adding this override method to my adapter:

@Override
public long getItemId(int position) {
   return position;
}

I have tried using recyclerView.setItemViewCacheSize(10);; this only pushes the problem index further out. I have also tried using setHasStableIds(true);.

It seems like this must be a caching issue, but I don't know what would be causing the issue in the first place.

This a greatly reduced version of my adapter. The full class does use two different view types, but I'm about 96% sure that is not the cause of the issue.

public class InventoryListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
   //global view controls declared
   private TextView name;   
   private List<Items> dataSet;

   public class RowViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
      private MyListener mListener;
      RowViewHolder (View v, MyListener listener) {
         super(v);
         //set view control ids
         name = v.findViewById(R.id.name);
         v.setOnClickListener(this);
      }
      //override onClick...
   }

   @Override
   public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
      RowViewHolder rowHolder = (RowViewHolder) holder;
      //set control values from database
      name.setText(someItem.getName());
   }

   @Override
   public int getItemCount() {
      return dataSet.size();
   }

   @Override
   public int getItemViewType(int position) {
      //returns 1 or 2 based on data type
   }

   @Override
   public long getItemId(int position) {
      return position;
   }

   //also, methods to update dataSet, delete items, and get item at position

If you would like to see the full project, you can find it here.

I would love to have a RecyclerView that actually scrolls through all of my data correctly!

EDIT: As requested, the full adapter code. Please pardon the mess!

public class InventoryListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private RecyclerViewClickListener myListener;
    private List<UserInventory> dataSet = new ArrayList<>();
    private List<Unit> itemUnits = new ArrayList<>(); //stores constant values from read-only sqlite database
    private TextView itemName, itemQuantity, itemNameLifeBar, itemQuantityLifeBar;
    private ProgressBar lifeBar;
    private UserInventory deletedItem;
    private int deletedPosition;

    public class RowViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
        private RecyclerViewClickListener rowListener;
        RowViewHolder  (View v, RecyclerViewClickListener listener) {
            super(v);
            itemName = v.findViewById(R.id.item_name);
            itemQuantity = v.findViewById(R.id.item_quantity);
            v.setOnClickListener(this);
        }
        @Override
        public void onClick(View view) {
            myListener.onClick(view, getAdapterPosition());
        }
    }

    public class LifeBarViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
        private RecyclerViewClickListener rowListener;
        LifeBarViewHolder (View v, RecyclerViewClickListener listener) {
            super(v);
            itemNameLifeBar = v.findViewById(R.id.item_name_trackable);
            itemQuantityLifeBar = v.findViewById(R.id.item_quantity_trackable);
            lifeBar = v.findViewById(R.id.lifebar);
            myListener = listener;
            v.setOnClickListener(this);
        }
        @Override
        public void onClick(View view) {
            myListener.onClick(view, getAdapterPosition());
        }
    }

    InventoryListAdapter(RecyclerViewClickListener listener) {
        myListener = listener;
        setHasStableIds(true);
    }

    //empty constructor
    InventoryListAdapter() {}

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        Context context = parent.getContext();
        switch (viewType) {
            case 1:
                View v = LayoutInflater.from(context).inflate(R.layout.inventory_list_item, parent, false);
                return new RowViewHolder(v, myListener);
            case 2:
                View view = LayoutInflater.from(context).inflate(R.layout.inventory_list_trackable, parent, false);
                return new LifeBarViewHolder(view, myListener);
        }
        //shouldn't happen
        View view = LayoutInflater.from(context).inflate(R.layout.inventory_list_item, parent, false);
        return new RowViewHolder(view, myListener);
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        switch(holder.getItemViewType()) {
            case 1:
                RowViewHolder rowHolder = (RowViewHolder) holder;
                UserInventory item = dataSet.get(position);
                Unit unit = itemUnits.get(position);
                itemName.setText(item.getItemName());
                if (item.isQuantify()) { //check if item is actually quantified first
                    itemQuantity.setText(item.getQuantity() + " " +  unit.getAbbreviation());
                } else {                 //otherwise, quantity needs to be empty string
                    itemQuantity.setText("");
                }
                break;
            case 2:
                LifeBarViewHolder lifebarHolder = (LifeBarViewHolder) holder;
                UserInventory item2 = dataSet.get(position);
                Unit unit2 = itemUnits.get(position);
                itemNameLifeBar.setText(item2.getItemName());
                itemQuantityLifeBar.setText(item2.getQuantity() + " " + unit2.getAbbreviation());
                lifeBar.setMax(item2.getMaxQuantity());
                lifeBar.setProgress(item2.getQuantity());
                break;
        }
    }

    public void updateData(List<UserInventory> data, final Context context) {
        if (data == null) { return; }
        UnitDao unitDao = new UnitDao(context); //pulls constant values from read-only sqlite database
        dataSet.clear();
        itemUnits.clear();
        dataSet.addAll(data);
        for (int i=0; i<dataSet.size(); i++) {
            //Log.e("_IDs", String.valueOf(dataSet.get(i).get_ID()));
            itemUnits.add(unitDao.getUnitById(dataSet.get(i).getUnit()));
        }
        notifyDataSetChanged();
    }

    public UserInventory deleteItem(int position) {
        deletedItem = dataSet.get(position);
        deletedPosition = position;
        dataSet.remove(position);
        itemUnits.remove(position);
        //notifyDataSetChanged();
        notifyItemRemoved(position);
        notifyItemRangeChanged(position, getItemCount());
        //showUndoSnackBar();
        return deletedItem;
    }

    public UserInventory getItemAtPosition(int position) {
        return dataSet.get(position);
    }

    @Override
    public int getItemCount() {
        return dataSet.size();
    }

    @Override
    public int getItemViewType(int position) {
        UserInventory item = dataSet.get(position);
        if (item.isMultiUnit()) {
            //is quantified with target max
            return 2;
        } else {
            return 1;
        }
    }

    @Override
    public long getItemId(int position) {
        return position;
    }
}
NerdyGinger
  • 406
  • 7
  • 13
  • It can happen when either your ViewHolder layout is super-complex or if you fetch your data from a database each time you need it. Is it one of your cases ? – cesarmarch Aug 21 '19 at 18:15
  • I do all of my database calls in my update method, which is called from an observer in my fragment's onCreateView and sets the dataSet value; as for the layouts... I wouldn't call them *super* complex, they only have 2-3 controls in them. – NerdyGinger Aug 21 '19 at 19:05
  • it probably has nothing to do with your database or your layouts being complex (dont know how that can change anything) if i had to guess it has to do with your method for either `getItemId` or your adapter binding logic – a_local_nobody Aug 21 '19 at 19:15
  • I don't see anything that stands out as problematic in the code you've posted. Just to get a clearer picture of the situation, could you post your full adapter code? – Sammy T Aug 21 '19 at 19:25

1 Answers1

0

Change your viewHolder to static class, and define all view items as local variable. Then access the view items through holder from onBindViewHolder

That might help.

iMahfuzurX
  • 11
  • 4