Showing posts with label adapter. Show all posts
Showing posts with label adapter. Show all posts

Friday, May 5, 2017

RecyclerView Items are not changing

Leave a Comment

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)

6 Answers

Answers 1

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

Answers 2

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.

Answers 3

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.

Answers 4

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.

Answers 5

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

Answers 6

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) { 
Read More

Saturday, April 1, 2017

RecyclerView Items are not changing

Leave a Comment

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 mFragmentList.get(position).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;     } } 

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

3 Answers

Answers 1

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

Answers 2

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.

Answers 3

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.

Read More

Thursday, March 2, 2017

create library to override operator()* of iterator - risk dangling pointer

Leave a Comment

I am trying to create my own boost::adaptors::transformed.
Here is the related boost code.

Here is its usage (modified from a SO answer by LogicStuff):-

C funcPointer(B& b){      //"funcPointer" is function convert from "B" to "C"     return instance-of-C }  MyArray<B> test;  //<-- any type, must already has begin() & end()  for(C c : test | boost::adaptor::transformed(funcPointer)) {     //... something .... } 

The result will be the same as :-

for(auto b : test) {     C c = funcPointer(b);     //... something ... } 

My Attempt

I created CollectAdapter that aim to work like boost::adaptor::transformed.
It works OK in most common cases.

Here is the full demo and back up. (same as below code)

The problematic part is CollectAdapter - the core of my library.
I don't know whether I should cache the collection_ by-pointer or by-value.

CollectAdapter encapsulates underlying collection_ (e.g. pointer to std::vector<>) :-

template<class COLLECTION,class ADAPTER>class CollectAdapter{     using CollectAdapterT=CollectAdapter<COLLECTION,ADAPTER>;     COLLECTION* collection_;    //<---- #1  problem? should cache by value?     ADAPTER adapter_;           //<---- = func1 (or func2)     public: CollectAdapter(COLLECTION& collection,ADAPTER adapter){         collection_=&collection;         adapter_=adapter;     }     public: auto begin(){         return IteratorAdapter<             decltype(std::declval<COLLECTION>().begin()),             decltype(adapter_)>             (collection_->begin(),adapter_);     }     public: auto end(){ ..... } }; 

IteratorAdapter (used above) encapsulates underlying iterator, change behavior of operator* :-

template<class ITERATORT,class ADAPTER>class IteratorAdapter : public ITERATORT {     ADAPTER adapter_;     public: IteratorAdapter(ITERATORT underlying,ADAPTER adapter) :         ITERATORT(underlying),         adapter_(adapter)     {   }     public: auto operator*(){         return adapter_(ITERATORT::operator*());     } }; 

CollectAdapterWidget (used below) is just a helper class to construct CollectAdapter-instance.

It can be used like:-

int func1(int i){   return i+10;   } int main(){     std::vector<int> test; test.push_back(5);     for(auto b:CollectAdapterWidget::createAdapter(test,func1)){         //^ create "CollectAdapter<std::vector<int>,func1>" instance          //here, b=5+10=15     } }   

Problem

The above code works OK in most cases, except when COLLECTION is a temporary object.

More specifically, dangling pointer potentially occurs when I create adapter of adapter of adapter ....

int func1(int i){   return i+10;    } int func2(int i){   return i+100;   } template<class T> auto utilityAdapter(const T& t){     auto adapter1=CollectAdapterWidget::createAdapter(t,func1);     auto adapter12=CollectAdapterWidget::createAdapter(adapter1,func2);     //"adapter12.collection_" point to "adapter1"     return adapter12;     //end of scope, "adapter1" is deleted     //"adapter12.collection_" will be dangling pointer } int main(){     std::vector<int> test;     test.push_back(5);     for(auto b:utilityAdapter(test)){         std::cout<< b<<std::endl;   //should 5+10+100 = 115     } } 

This will cause run time error. Here is the dangling-pointer demo.

In the real usage, if the interface is more awesome, e.g. use | operator, the bug will be even harder to be detected :-

//inside "utilityAdapter(t)" return t|func1;        //OK! return t|func1|func2;  //dangling pointer 

Question

How to improve my library to fix this error while keeping performance & robustness & maintainablilty near the same level?

In other words, how to cache data or pointer of COLLECTION (that can be adapter or real data-structure) elegantly?

Alternatively, if it is easier to answer by coding from scratch (than modifying my code), go for it. :)

My workarounds

The current code caches by pointer.
The main idea of workarounds is to cache by value instead.

Workaround 1 (always "by value")

Let adapter cache the value of COLLECTION.
Here is the main change:-

COLLECTION collection_;    //<------ #1  //changed from   .... COLLECTION* collection_; 

Disadvantage:-

  • Whole data-structure (e.g. std::vector) will be value-copied - waste resource.
    (when use for std::vector directly)

Workaround 2 (two versions of library, best?)

I will create 2 versions of the library - AdapterValue and AdapterPointer.
I have to create related classes (Widget,AdapterIterator,etc.) as well.

  • AdapterValue - by value. (designed for utilityAdapter())
  • AdapterPointer - by pointer. (designed for std::vector)

Disadvantage:-

  • Duplicate code a lot = low maintainability
  • Users (coders) have to be very conscious about which one to pick = low robustness

Workaround 3 (detect type)

I may use template specialization that do this :-

If( COLLECTION is an "CollectAdapter" ){ by value }   Else{ by pointer }     

Disadvantage:-

  • Not cooperate well between many adapter classes.
    They have to recognize each other : recognized → cache by value.

Sorry for very long post.

0 Answers

Read More

Saturday, March 19, 2016

Nested recylerview lag while first few scrolls and then scrolls smoothly?

Leave a Comment

I am using nested RecyclerView. Means inside a vertical RecyclerView I have multiple horizontal recycler view

I am attaching adapter to horizontal recylerviews inside onBindViewHolder method of parent RecyclerView as follows.

@Override public void onBindViewHolder(final MainViewHolder holder, final int position) {     switch (holder.getItemViewType()) {         case TYPE_PRODUCT:             ((ListHolderProduct) holder).itemTitle.setText(browseCategoryHomePageItems.get(position).displayName.toUpperCase());             ((ListHolderProduct) holder).recyclerView.setAdapter(new CarouselProductsRecyclerAdapter(context                     , browseCategoryHomePageItems.get(position).products                     , R.layout.activity_categoryhome_products_grid_item                     , nestedRecyclerItemClickedListener                     , position));             break;         case TYPE_DEAL:             ((ListHolderDeal) holder).itemTitle.setText(browseCategoryHomePageItems.get(position).displayName.toUpperCase());             ((ListHolderDeal) holder).recyclerView.setAdapter(new CarouselDealsRecyclerAdapter(context                     , browseCategoryHomePageItems.get(position).dealItems                     , R.layout.activity_categoryhome_deals_grid_item                     , nestedRecyclerItemClickedListener                     , position));             break;             //few more types like this     } } 

Now whenever I scroll page it is lagging a bit since I am attaching adapter to horizontal RecyclerView on OnBindViewHolder

And there can be N Number of TYPE_PRODUCT or any type of horizontal lists. Means there can be more that one horizontal lists of same type.

Any idea how can I optimize this thing and improve the scroll speed.

It is lagging since setAdapter is called every time for list previously.

Update on this I am extending LinearLayoutManager and in that I am setting extraLayout space which has fixed my issue but I don't know this is right way or not I am setting extra space as below.

 layoutManager.setExtraLayoutSpace(2 * this.getResources().getDisplayMetrics().heightPixels); 

and follwoing is custom layout manager class

public class PreCachingLayoutManager extends LinearLayoutManager { private static final int DEFAULT_EXTRA_LAYOUT_SPACE = 600; private int extraLayoutSpace = -1; private Context context;  public PreCachingLayoutManager(Context context) {     super(context);     this.context = context; }  public PreCachingLayoutManager(Context context, int extraLayoutSpace) {     super(context);     this.context = context;     this.extraLayoutSpace = extraLayoutSpace; }  public PreCachingLayoutManager(Context context, int orientation, boolean reverseLayout) {     super(context, orientation, reverseLayout);     this.context = context; }  public void setExtraLayoutSpace(int extraLayoutSpace) {     this.extraLayoutSpace = extraLayoutSpace; }  @Override protected int getExtraLayoutSpace(RecyclerView.State state) {     if (extraLayoutSpace > 0) {         return extraLayoutSpace;     }     return DEFAULT_EXTRA_LAYOUT_SPACE; } 

}

3 Answers

Answers 1

I had the same problem with scrolling, because of loading images while scrolling. So you need to use Picasso library for loading your images, and to make pause_tag if you are using onScrollListener.

In your onBindViewHolder Picasso.with(context) .load(foodData.getRecipe_resize_image_url()) .resize(width, height) .placeholder(R.drawable.empty_image) .tag("resume_tag") .into(mainViewHolder.food_picture);

In your onScrollListener

@Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) {     super.onScrollStateChanged(recyclerView, newState);     final Picasso picasso = Picasso.with(context);     if (newState == RecyclerView.SCROLL_STATE_IDLE) {         picasso.resumeTag("resume_tag");     } else {         picasso.pauseTag("resume_tag");     }  } 

Answers 2

Do not create horizontal adapter every time in onBindViewHolder, instead in each ViewHolder class(ListHolderDeal, ListHolderProduct) create appropriate adapter. then in onBindViewHolder of vertical Recyclerview just replace the data of that adapter and if the horizontal RecyclerView dose not have an adapter set it with the viewholder adapter. if it dose, replace the data set of that adapter and call notifyDataSetChange. With this approach you can use adapter pool implicitly so the GC may less bother you.

I hope it helps.

Answers 3

Please check whether you have overrided getItemViewType(int position) method, if not please override this method and say how many type of views your recyclerview is going to handle, from the above code there are two types of view one for TYPE_PRODUCT and other for TYPE_DEAL.

This particular method if overriden and returns the type count correctly, it prevents unwanted layout inflation for the already available typed view(in recycler cache) which increases the performance drastically.

For example of implementing multiple typed recyclerview, please refer this.

Reference : http://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#getItemViewType%28int%29

Hope this helps!!! Please let me know if you have any issues with it. Thanks.

Read More