bergler77
bergler77

Reputation: 372

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.

Upvotes: 11

Views: 20058

Answers (5)

Rahul Tiwari
Rahul Tiwari

Reputation: 6978

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>

Upvotes: 1

bergler77
bergler77

Reputation: 372

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!

Upvotes: 6

Schroooom
Schroooom

Reputation: 29

Just reset the Adapter like this

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

recList should be you RecyclerView.

Upvotes: 2

Ankit Tale
Ankit Tale

Reputation: 2004

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

Upvotes: 0

Sohail Zahid
Sohail Zahid

Reputation: 8149

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

Upvotes: 0

Related Questions