[Solved] How to force RecyclerView.Adapter to call the onBindViewHolder() on all elements

I have a VerticalGridView that is using a RecyclerView.Adapter to populate the elements. I have discovered that the onBindViewHolder() method does not get called if the potential element is off of the viewport. Unfortunately, this is causing a NullPointerException from a different method because I am catching a TextView reference in the onBindViewHolder() method and passing it to an outside variable for later manipulation.

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    final ViewHolder viewHolder = (ViewHolder) holder;
    viewHolder.txtCategoryName.setText(categories.get(position).getStrCategory());
    categories.get(position).setTxtViewReference(viewHolder.txtCategoryDefectTotal);
    viewHolder.categoryBoxRoot.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            for(CategoryListItem catItem : categories){
                if(catItem.getStrCategory().equals(viewHolder.txtCategoryName.getText())){
                    int index = Defects.getInstance().getCategories().indexOf(catItem) + 1;
                    MainInterface.grids.get(index).bringToFront();
                    MainInterface.grids.get(index).setVisibility(View.VISIBLE);
                    for(VerticalGridView grid : MainInterface.grids){
                        int gridIndex = MainInterface.grids.indexOf(grid);
                        if(gridIndex != index){
                            grid.setVisibility(View.INVISIBLE);
                        }
                    }
                    break;
                }
            }

        }
    });

From what I understand, the reference to the TextView gets created when the Viewholder object is instantiated.

public class ViewHolder extends RecyclerView.ViewHolder {

    public TextView txtCategoryName;
    public TextView txtCategoryDefectTotal;
    public View categoryBoxRoot;

    public ViewHolder(View itemView) {
        super(itemView);
        txtCategoryName = (TextView) itemView.findViewById(R.id.textViewCategoryName);
        txtCategoryDefectTotal = (TextView) itemView.findViewById(R.id.textViewCategoryTotalDefects);
        categoryBoxRoot = itemView.findViewById(R.id.root_category_box);
    }
}

Is there a way to force onBindViewHolder() to be called on all elements at least one time when the Adapter is instantiated?

I attempted the suggestions here without any success.

I understand that forcing onBindViewHolder() on all elements would work against the whole purpose of the RecycleView.Adapter. Thus, I am open to any other suggestions on how I can catch that TextView reference.

As a temporary fix to this problem, I am able to use a try catch block around the method that generates the NullPointerException. However, I am concerned that the lack of the reference means I could introduce errors in the future.

Solution #1:

The easiest solution for this problem is to scroll down to the bottom of the grid and then back up to the top. This cannot be done in the onCreate() method though because the grid is not technically visible at that time. Instead, it should be called in the onResume() method of the activity.

@Override
public void onResume(){
    super.onResume();
    VerticalGridView defectGrid = grids.get(0);
    RecyclerView.Adapter adapter = defectGrid.getAdapter();
    defectGrid.smoothScrollToPosition(adapter.getItemCount()-1);
    defectGrid.smoothScrollToPosition(0);
}

This was a good attempt at a solution but unfortunately it still does not work. While it does get the reference to make the method work, it does NOT necessarily have the right reference. I found that the reference can end up pointing at a different TextView as the RecycleView.Adapter reuses views to display them in different areas.

SOLUTION:

Mark Keen was right when he said to use notifyDataSetChanged(). I got it to work by fixing my onBindViewHolder() method to work properly.

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    final ViewHolder viewHolder = (ViewHolder) holder;
    viewHolder.txtCategoryName.setText(categories.get(position).getStrCategory());
    viewHolder.txtCategoryDefectTotal.setText(String.valueOf(categories.get(position).getTotalDefectsInCategory()));
}

I also changed my data object so it holds an int value instead of a reference to the TextView since the above proved that reference was invalid. Finally, I added a call to my Adapter when I pressed my custom back button.

grids.get(0).getAdapter().notifyDataSetChanged();

Thank you everyone who contributed!

Respondent: bergler77

Solution #2:

Just reset the Adapter like this

RecyclerView.Adapter adapter = recList.getAdapter();
recList.setAdapter(null);
recList.setAdapter(adapter);

recList should be you RecyclerView.

Respondent: Schroooom

Solution #3:

Understand Flow

public class ThemeGridAdapter extends RecyclerView.Adapter<ThemeGridAdapter.ViewHolder> {
    private OnItemClickedListener listener;
    private List<Theme> mDataset;

    public static class ViewHolder extends RecyclerView.ViewHolder {
        public TextView title;
        public TextView subtitle;
        //        public SimpleDraweeView image;
        public ImageView img;
        public ViewGroup container;

        public ViewHolder(RelativeLayout v) {
            super(v);
            container = v;
            title = (TextView) v.findViewById(R.id.theme_name_text);
            subtitle = (TextView) v.findViewById(R.id.theme_name_type);
//            image = (SimpleDraweeView) v.findViewById(R.id.cell_img);
            img = (ImageView) v.findViewById(R.id.cell_img);
        }
    }

    public ThemeGridAdapter(List<Theme> dataset) {
        mDataset = dataset;
    }

    @Override
    public ThemeGridAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
                                                          int viewType) {
        RelativeLayout v = (RelativeLayout) LayoutInflater.from(parent.getContext())
                .inflate(R.layout.cell, parent, false);
        return new ViewHolder(v);
    }

    @Override
    public void onBindViewHolder(final ViewHolder holder, final int position) {
        holder.title.setText(mDataset.get(position).getTitle());
        holder.subtitle.setText(mDataset.get(position).getAuthor());
//        Uri uri = Uri.parse(mDataset.get(position).getUrl());
        holder.img.setImageResource(mDataset.get(position).getImage());
//        holder.image.setImageURI(uri);
        holder.container.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (listener != null)
                    listener.onItemClicked(holder.getAdapterPosition());
            }
        });

    }

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

    public void setListener(OnItemClickedListener listener) {
        this.listener = listener;
    }
}
Respondent: Sohail Zahid

Solution #4:

Check the style of binding view

  public class CrimeListFragment extends Fragment {


    private static final String SAVED_SUBTITLE_VISIBLE = "subtitle";

    private static final int REQUEST_CRIME = 1;

    private RecyclerView mRecyclerView;
    private CrimeAdapter mCrimeAdapter;

    private boolean mSubtitleVisible;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setHasOptionsMenu(true);
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_crime_list, container, false);

        mRecyclerView = (RecyclerView) view.findViewById(R.id.crime_recycler_view);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));

        if (savedInstanceState != null){
            mSubtitleVisible = savedInstanceState.getBoolean(SAVED_SUBTITLE_VISIBLE);
        }
        updateUI();


        return view;
    }

    private void updateUI() {

        CrimeLab crimeLab = CrimeLab.get(getActivity());
        List<Crime> crimes = crimeLab.getCrimes();

        if (mCrimeAdapter == null) {
            mCrimeAdapter = new CrimeAdapter(crimes);
            mRecyclerView.setAdapter(mCrimeAdapter);
        } else {
            mCrimeAdapter.setCrimes(crimes);
            mCrimeAdapter.notifyDataSetChanged();
        }
        updateSubtitle();
    }

    private class CrimeHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

        private TextView mTitleTextView;
        private TextView mDateTextView;
        private CheckBox mSolvedCheckBox;
        private Crime mCrime;


        public CrimeHolder(View itemView) {
            super(itemView);

            itemView.setOnClickListener(this);

            mTitleTextView = (TextView) itemView.findViewById(R.id.list_item_crime_title_text_view);
            mDateTextView = (TextView) itemView.findViewById(R.id.list_item_crime_date_text_view);
            mSolvedCheckBox = (CheckBox) itemView.findViewById(R.id.list_item_crime_solved_checkbox);

        }

        public void bindCrime(Crime crime) {

            mCrime = crime;

            mTitleTextView.setText(mCrime.getTitile());
            mDateTextView.setText(mCrime.getDate().toString());
            mSolvedCheckBox.setChecked(mCrime.isSolved());
        }

        @Override
        public void onClick(View v) {

            /*Toast.makeText(getActivity(),mCrime.getTitile() + "clicked!", Toast.LENGTH_SHORT).show();*/

            /*Intent for calling activity from fragment*/
           /* Intent intent = new Intent(getActivity(), CrimeActivity.class);*/
            /*Intent intent = CrimeActivity.newIntent(getActivity(),mCrime.getId());*/
            Intent intent = CrimePagerActivity.newIntent(getActivity(), mCrime.getId());
            /*startActivityForResult(intent,REQUEST_CRIME);*/
            startActivity(intent);
        }
    }

    private class CrimeAdapter extends RecyclerView.Adapter<CrimeHolder> {

        private List<Crime> mCrimes;

        public CrimeAdapter(List<Crime> crimes) {

            mCrimes = crimes;
        }

        @Override
        public CrimeHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            LayoutInflater layoutInflater = LayoutInflater.from(getActivity());

            View view = layoutInflater.inflate(R.layout.list_item_crime, parent, false);
            return new CrimeHolder(view);

        }

        @Override
        public void onBindViewHolder(CrimeHolder holder, int position) {

            Crime crime = mCrimes.get(position);
            /*holder.mTextView.setText(crime.getTitile());*/
            holder.bindCrime(crime);

        }

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

        public void setCrimes(List<Crime> crimes) {

            mCrimes = crimes;
        }
    }


    @Override
    public void onResume() {
        super.onResume();
        updateUI();
    }


    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == REQUEST_CRIME) {

        }
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        super.onCreateOptionsMenu(menu, inflater);
        inflater.inflate(R.menu.fragment_crime_list, menu);

        MenuItem subtitleItem = menu.findItem(R.id.menu_item_show_subtitle);
        if (mSubtitleVisible){
            subtitleItem.setTitle(R.string.hide_subtitle);
        }else {
            subtitleItem.setTitle(R.string.show_subtitle);
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {

        switch (item.getItemId()) {

            case R.id.menu_item_new_crime:

                Crime crime = new Crime();
                CrimeLab.get(getActivity()).addCrime(crime);
                Intent intent = CrimePagerActivity.newIntent(getActivity(), crime.getId());
                startActivity(intent);
                return true;

            case R.id.menu_item_show_subtitle:

                mSubtitleVisible = !mSubtitleVisible;
                getActivity().invalidateOptionsMenu();
                updateSubtitle();
                return true;

            default:
                return super.onOptionsItemSelected(item);
        }
    }

    private void updateSubtitle(){

        CrimeLab crimeLab = CrimeLab.get(getActivity());
        int crimeCount = crimeLab.getCrimes().size();

        String subtitle = getString(R.string.subtitle_format,crimeCount);
        if (!mSubtitleVisible){

            subtitle  = null;
        }
        AppCompatActivity appCompatActivity = (AppCompatActivity)getActivity();
        appCompatActivity.getSupportActionBar().setSubtitle(subtitle);

    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putBoolean(SAVED_SUBTITLE_VISIBLE,mSubtitleVisible);
    }
}
Respondent: Venky

Solution #5:

This worked for me and Forced onBindViewHolder() to be called

 mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
            mAdapter.notifyDataSetChanged();
        }
    });

Solution #6:

wrap your RecyclerView in a NestedScrollView that will force all the items to be bound in advance.

<androidx.core.widget.NestedScrollView
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rvGrid"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:nestedScrollingEnabled="false" />
</androidx.core.widget.NestedScrollView>
Respondent: Rahul Tiwari

The answers/resolutions are collected from stackoverflow, are licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0 .

Leave a Reply

Your email address will not be published.