android – RecyclerView Items are not changing-ThrowExceptions

Exception or error:

I have a RecyclerView in a fragment which is repeating in TabLayout. I am having the problem of unchanged view in RecyclerView. I have a spinner on each tab. I want to change the data when spinner items get selected.

My cases:

  1. when switching between tabs – items changed
  2. when selecting another value in the spinner in the first tab –items not changed. (but data is changing in adapter class.Ie. First, it is not null then null during selection. But the first not nulled data is not appearing, its replacing with null. Found it using breakpoints).

    Note: In this case, when switching the tab, the items get changed to
    the spinner selected items in the previous tab. And then it disappears
    and displaying the current items in the tab.

  3. when selecting another value in the spinner in the last tab –items changed.

My view pager adapter class


public class StudentViewPagerAdapter extends FragmentStatePagerAdapter {
    private final List<StudentList> mFragmentList = new ArrayList<>();
    private final List<Clazz> mFragmentTitleList = new ArrayList<>();
    public StudentViewPagerAdapter(FragmentManager fm) {
        super(fm);
    }
    public void addFragment(StudentList fragment,Clazz clazz){
        mFragmentList.add(fragment);
        mFragmentTitleList.add(clazz);
    }
    @Override
    public Fragment getItem(int position) {
        return StudentList.newInstance(mFragmentTitleList.get(position));
    }

    @Override
    public int getCount() {
        return mFragmentList.size();
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return mFragmentTitleList.get(position).getName();
    }
}

my RecyclerView adapter class

public class PeopleAdapter extends RecyclerView.Adapter<PeopleAdapter.MyViewHolder> implements View.OnClickListener {
    private List<Student> dataList;
    private Context context;
    private Clicker clicker;
    public PeopleAdapter(List<Student> data, Context context, Clicker clicker) {
        this.dataList = data;
        this.context = context;
        this.clicker = clicker;
    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.people_list_item, parent, false);

        return new MyViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        Student data=dataList.get(position);
        holder.email.setText(data.getEmail());
        holder.name.setText(data.getName());
        holder.phone.setText(data.getPhone());
        Glide.with(context).load(Method.getImageUrl(MyConfiguration.STUDENT_IMAGE_URL,
                data.getStudentId())).asBitmap().into(holder.profilePic);
        holder.edit.setOnClickListener(this);
        holder.edit.setTag(position);
    }

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

    @Override
    public void onClick(View v) {
        clicker.OnItemClicked((int) v.getTag(),null);
    }


    static class MyViewHolder extends RecyclerView.ViewHolder {
    @BindView(R.id.name)
        TextView name;
        @BindView(R.id.email)
        TextView email;
        @BindView(R.id.phone)
        TextView phone;
        @BindView(R.id.image)
        ImageView profilePic;
        @BindView(R.id.imageedit)
        ImageView edit;
    MyViewHolder(View view) {
        super(view);
        ButterKnife.bind(this,view);
    }
}

}

tab fragments

    public class StudentList extends Fragment implements SectionChanger {
        @BindView(R.id.studentlist)
        RecyclerView mRecyclerview;
        CompositeDisposable disposable;
        private Unbinder unbinder;
        private Clazz clazz;
        private Requester requester;

        public StudentList() {
            StudentInformation.bindSectionChangeListener(this);
        }
        public static StudentList newInstance(Clazz clazz) {
            StudentList fragment=new StudentList();
            Bundle args = new Bundle();
            args.putSerializable(MyConfiguration.SECTIONS, clazz);
            fragment.setArguments(args);
            return fragment;
        }
        @Override
        public void onDestroyView() {
            super.onDestroyView();
            unbinder.unbind();
            disposable.clear();
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            // Inflate the layout for this fragment
            View view = inflater.inflate(R.layout.fragment_student_list, container, false);
            unbinder = ButterKnife.bind(this, view);
            LinearLayoutManager mLayoutManager = new LinearLayoutManager(getActivity());
            mLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
            mRecyclerview.setLayoutManager(mLayoutManager);
            initializeRetrofit();
            if (getArguments() != null) {
                clazz = (Clazz) getArguments().getSerializable(MyConfiguration.SECTIONS);
                loadStudentJson(clazz != null ? clazz.getClassId() : null,
                        clazz != null ? clazz.getSections().get(0).getSectionId() : null);
            }
            return view;
        }

        /**
         * Load students list
         */
        public void loadStudentJson(String class_id,String section_id) {


            disposable = new CompositeDisposable(requester.getStudentsInSection(class_id,section_id)
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribeOn(Schedulers.io())
                    .subscribe(
                            this::handleResponse,
                            this::handleError
                    )
            );
        }

        private void handleResponse(List<Student> list) {
            PeopleAdapter adapter=new PeopleAdapter(list, getActivity(),
                    (position, name) -> Toast.makeText(getActivity(), position, Toast.LENGTH_LONG).show());
            mRecyclerview.setAdapter(adapter);
            adapter.notifyDataSetChanged();
        }

        private void handleError(Throwable error) {
            Toast.makeText(getActivity(), "Error " + error.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
        }

        @Override
        public void ChangeData(Section section) {
            loadStudentJson(section.getClassId(),section.getSectionId());
        }
        public void initializeRetrofit(){
            HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
            interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
            CookieHandler handler=new Cookies(getActivity());
            //ClearableCookieJar cookieJar = new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(getActivity()));
            OkHttpClient client = new OkHttpClient.Builder()
                    .addInterceptor(interceptor)
                    .cookieJar(new JavaNetCookieJar(handler))
                    .build();

            requester = new Retrofit.Builder()
                    .baseUrl(MyConfiguration.BASE_URL)
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .addConverterFactory(JacksonConverterFactory.create())
                    .client(client)
                    .build().create(Requester.class);
        }
    }

Viewpager setup fragment

public class StudentInformation extends Fragment implements TabLayout.OnTabSelectedListener, AdapterView.OnItemSelectedListener {
    // TODO: Rename parameter arguments, choose names that match
    // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
    private static final String ARG_PARAM1 = "param1";
    private static final String ARG_PARAM2 = "param2";
    private Unbinder unbinder;
    // TODO: Rename and change types of parameters
    private String mParam1;
    private String mParam2;
    CompositeDisposable disposable;
    List<Section> sectionsList=new ArrayList<>();
    private OnConnectingFragments mListener;
    @BindView(R.id.tabs)
    TabLayout mTabLayout;
    @BindView(R.id.viewpager)
    ViewPager viewPager;
    @BindView(R.id.secSelector)
    Spinner spinner;
    @BindView(R.id.className)
    TextView className;
    private static SectionChanger sectionChanger;
    public StudentInformation() {
        // Required empty public constructor
    }

    /**
     * Use this factory method to create a new instance of
     * this fragment using the provided parameters.
     *
     * @param param1 Parameter 1.
     * @param param2 Parameter 2.
     * @return A new instance of fragment StudentInformation.
     */
    // TODO: Rename and change types and number of parameters
    public static StudentInformation newInstance(String param1, String param2) {
        StudentInformation fragment = new StudentInformation();
        Bundle args = new Bundle();
        args.putString(ARG_PARAM1, param1);
        args.putString(ARG_PARAM2, param2);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            mParam1 = getArguments().getString(ARG_PARAM1);
            mParam2 = getArguments().getString(ARG_PARAM2);
        }
    }
    @Override
    public void onDestroyView() {
        super.onDestroyView();
        unbinder.unbind();
        disposable.clear();
    }
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view=inflater.inflate(R.layout.fragment_student_information, container, false);
        unbinder = ButterKnife.bind(this, view);
        LoadDataAndSetupViewPager();
        //setupViewPager(viewPager);
        mTabLayout.setupWithViewPager(viewPager);
        mTabLayout.addOnTabSelectedListener(this);
        spinner.setOnItemSelectedListener(this);
//        ArrayAdapter<String> arrayAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_dropdown_item, MyConfiguration.CLASS_SECTIONS);
//        spinner.setAdapter(arrayAdapter);

        return view;
    }

    // TODO: Rename method, update argument and hook method into UI event
    public void onButtonPressed(Fragment fragment,String tag) {
        if (mListener != null) {
            mListener.onClickedMenu(fragment,tag);
        }
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (context instanceof OnConnectingFragments) {
            mListener = (OnConnectingFragments) context;
        } else {
            throw new RuntimeException(context.toString()
                    + " must implement OnFragmentInteractionListener");
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mListener = null;
    }
    /**
     * This interface must be implemented by activities that contain this
     * fragment to allow an interaction in this fragment to be communicated
     * to the activity and potentially other fragments contained in that
     * activity.
     * <p>
     * See the Android Training lesson <a href=
     * "http://developer.android.com/training/basics/fragments/communicating.html"
     * >Communicating with Other Fragments</a> for more information.
     */
    /*private void setupViewPager(ViewPager viewPager) {
        StudentViewPagerAdapter adapter = new StudentViewPagerAdapter(getFragmentManager());
        //adapter.addFragment(new StudentList(),"exam");

        for (String claz: MyConfiguration.CLASS)
            adapter.addFragment(new StudentList(), claz);
        viewPager.setAdapter(adapter);
    }*/
    @OnClick(R.id.add)
    public void OnClicked(LinearLayout view){
        onButtonPressed(new AddStudent(),"addStudent");
    }

    @Override
    public void onTabSelected(TabLayout.Tab tab) {
        className.setText(String.format(getString(R.string.class_name), tab.getPosition()+1));
    }

    @Override
    public void onTabUnselected(TabLayout.Tab tab) {

    }

    @Override
    public void onTabReselected(TabLayout.Tab tab) {

    }
    public void LoadDataAndSetupViewPager() {
        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        ClearableCookieJar cookieJar =
                new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(getActivity()));
        OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build();

        Requester requester=new Retrofit.Builder()
                .baseUrl(MyConfiguration.BASE_URL)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(JacksonConverterFactory.create())
                .client(client)
                .build().create(Requester.class);
        disposable=new CompositeDisposable(requester.getClasses()    ////GETTING CLASSES////
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.io())
                .flatMapIterable(clazzs -> clazzs)
                .flatMap(clazz -> requester.getDivision(clazz.getClassId())  ////GETTING SECTIONS////
                        .observeOn(AndroidSchedulers.mainThread())
                        .subscribeOn(Schedulers.io())
                        .flatMapIterable(sections -> sections)
                        .doOnNext(section -> {sectionsList.add(section);
                            Log.v("section_id",section.getSectionId());})
                        .takeLast(1)
                        .map(section -> clazz)
                )
                .doOnNext(clazz -> {clazz.setSections(sectionsList);
                    Log.v("List Size",sectionsList.size()+"");
                    sectionsList=new ArrayList<>();
                })
                .toList()
                .subscribe(this::SetupViewPager, throwable -> Log.e("retroerror",throwable.toString())));

    }
    public void SetupViewPager(List<Clazz> classList){
        StudentViewPagerAdapter adapter = new StudentViewPagerAdapter(getFragmentManager());
        //adapter.addFragment(new StudentList(),"exam");

        for (Clazz claz: classList){
            adapter.addFragment(new StudentList(), claz);
        }

        viewPager.setAdapter(adapter);
        viewPager.setOffscreenPageLimit(3);
        viewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
            @Override
            public void onPageSelected(int position) {
                super.onPageSelected(position);
                List<Section>sections=classList.get(position).getSections();
                ArrayAdapter<Section> arrayAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_dropdown_item, sections);
                spinner.setAdapter(arrayAdapter);
            }
        });
    }

    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                Section section= (Section) parent.getItemAtPosition(position);
                sectionChanger.ChangeData(section);
    }

    @Override
    public void onNothingSelected(AdapterView<?> parent) {

    }
    public static void bindSectionChangeListener(SectionChanger changer){
        sectionChanger=changer;
    }
}

enter image description here

The question is: Why the data get unchanged when selecting options in the spinner sometimes? (look my cases)

How to solve:

try using getChildFragmentManager() instead of getFragmentManager() will solve your issue

###

I think you should implement onNothingSelected() as below

Section mCurrentSection;

@Override
public void onNothingSelected(AdapterView<?> parent) {
            int position = parent.getSelectedItemPosition();
            Section section= (Section) parent.getItemAtPosition(position);
            if (mCurrentSection == null || !mCurrentSection.equals(section)) {
                 mCurrentSection = section;
                 sectionChanger.ChangeData(section);
            }
}

Because, onNothingSelected is called instead of onItemSelected when you re-select an item of spinner.

###

The problem is mainly here:

 public StudentList() {
            StudentInformation.bindSectionChangeListener(this);
        }

This doesn’t assure that the fragment visible to the user is the last bound. You set viewPager.setOffscreenPageLimit(3); meaning that three fragments per time can be instantiated, so when you are on a fragment also the right and the left one are created.

This explain why the last one works well, because there isn’t a right fragment, and probably is the last one to be instantiated and bound.

Personally I would change the implementation handling the spinner inside the page fragment, since it acts only on the page fragment.

Solution with a setUserVisibleHint

Bind the page fragment to the main one when the fragment become visble to the user. Pay attention to memory leak and release the static reference

 @Override
  public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    if(isVisibleToUser){
      StudentInformation.bindSectionChangeListener(this);
    }
  }

Solution with a bus

Another quick solution could be to use a bus like this: http://square.github.io/otto/
In this way every fragment will subscribe to an event SelectedItemChanged and refresh them-self. The main fragment will post the updates every time the spinner selection is changed.

However the example is pretty big, so I’m not super sure that there aren’t other problems. Try to share a complete project to receive more specific help.

###

When you work with recycleview rely only on your model class filed values. Do all the changes to model class and create row views based on this model class fields.

i.e, you have to create or change values or view state based on the value of the model class fields.

For ex: if you want to make a row (position 30) invisible, then i will set a flag in model class called visibility and set it to false.

in this case whenever view redraws in UI, we will have to check for the flag and set the visibility based on that model class field value.

Like this you can create flags, content values, serialized params from API etc in model class. This method will make your reclycleview more consistent through out user interactions and API changes.

###

Check your StudentViewPagerAdapter

 @Override
    public Fragment getItem(int position) {
        return mFragmentList.get(position).newInstance(mFragmentTitleList.get(position));
    }

why do you call newInstance again?

Replace it with the following,
return mFragmentList.get(position);

###

Firstly, check that you add this line after

setadapter :
adapter.notifyDataSetChanged();

Another way is make final the MyViewHolder holder and int position

example.

public void onBindViewHolder(final MyViewHolder holder, final int position) {

Leave a Reply

Your email address will not be published. Required fields are marked *