All ViewPager fragments reference the same RecyclerView and / or Adapter

I have a snippet containing a RecyclerView to display events for a given day. I use ViewPager to split fragments for several days; Fragment for Saturday events and a fragment for Sunday events.

However, it seems that both fragments refer to the same RecyclerView and / or Adapter, as this is only the last tab (in this case, Sunday) whose events are displayed.

In my particular case, Saturday has two events, and on Sunday there are no events. Both fragments have empty RecyclerViews. To confirm my theory that it was caused by the last tab, I switched the date. This led to both RecyclerViews having two events (from Saturday).

Here is the appropriate code for the individual fragments:

public class EventListFragment extends Fragment{ private EventAdapter mEventAdapter; private static final String DATE_ARG = "eventDate"; public static EventListFragment newInstance(LocalDate date){ EventListFragment eventListFragment = new EventListFragment(); Bundle args = new Bundle(); args.putSerializable(DATE_ARG, date); eventListFragment.setArguments(args); return eventListFragment; } @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_event_list, container, false); // Setup recyclerview RecyclerView eventRecyclerView = (RecyclerView) view.findViewById(R.id.event_recycler_view); LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity()); layoutManager.setOrientation(LinearLayoutManager.VERTICAL); eventRecyclerView.setLayoutManager(layoutManager); // Get date LocalDate eventDate = (LocalDate) getArguments().getSerializable(DATE_ARG); // Set adapter mEventAdapter = new EventAdapter(getActivity(), getEvents(eventDate)); eventRecyclerView.setAdapter(mEventAdapter); return view; } } 

getEvents() is just a private function to return events for a given date. I used a debugger as well as unit tests to make sure it was working correctly. The debugger shows that it is typing the correct list for each fragment, but, as I explained, they are not displayed correctly.

Here is the appropriate code for the parent fragment:

 public class EventFragment extends Fragment { @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_event, container, false); // Get and set up viewpager final ViewPager viewPager = (ViewPager) view.findViewById(R.id.event_view_pager); EventFragmentAdapter eventFragmentAdapter = new EventFragmentAdapter(getFragmentManager(), getEventDates()); viewPager.setAdapter(eventFragmentAdapter); // Get and set up tablayout final TabLayout tabLayout = (TabLayout) view.findViewById(R.id.event_tabs); tabLayout.setTabGravity(TabLayout.GRAVITY_FILL); tabLayout.post(new Runnable() { @Override public void run() { tabLayout.setupWithViewPager(viewPager); } }); return view; } } 

Like the previous one, getEventDates() simply carries the dates on which events occur. For testing at the moment, I hard code the returned list of dates, since we do not yet have our database. I pulled out this method because I want the application to be able to function again in 2016, which may have different dates:

 private List<LocalDate> getEventDates(){ List<LocalDate> eventDates = new ArrayList<>(); eventDates.add(new LocalDate(2015, 10, 17)); eventDates.add(new LocalDate(2015, 10, 18)); return eventDates; } 

The last bit of the corresponding code for the FragmentStatePagerAdapter that I use for my ViewPager:

 public class EventFragmentAdapter extends FragmentStatePagerAdapter { private List<LocalDate> mEventDates; public EventFragmentAdapter(FragmentManager fragmentManager, List<LocalDate> eventDates){ super(fragmentManager); this.mEventDates = eventDates; } @Override public Fragment getItem(int i) { return EventListFragment.newInstance(mEventDates.get(i)); } @Override public int getCount() { return mEventDates.size(); } @Override public CharSequence getPageTitle(int position) { return mEventDates.get(position).dayOfWeek().getAsText(); } } 

Any ideas why both lists are always the same and based on the last tab in ViewPager? I assume that somehow they refer to the same RecyclerView or the same RecyclerViewAdapter, but I don't have any static fields for them, so I'm not sure how this happens.

+6
source share
3 answers

Long hunting and anti-climatic decision (as is the case with the most complex errors). Also a little unfair, since the error is not listed in the code above, I had to track your git project to figure this out. The error is in the EventAdapter :

 public class EventAdapter extends RecyclerView.Adapter<EventAdapter.ViewHolder> { private static final List<Event> mEvents; private final Context mContext; public EventAdapter(Context context, List<Event> events){ this.mContext = context; mEvents = events; } ... } 

mEvents is static! ... so it is shared across all instances of mEvents . This explains the error, as recent updates will set values ​​for all EventAdapters .

It looks like you created static static mEvents to access it in your ViewHolders . Instead, you can simply save a single event in the ViewHolder and remove the dangerous static modifier. On the flip side, cheers for open source projects!

+16
source

I saw your code, and I completely agree with Travor - you use a static member, which is replaced every time you create a new adapter (and therefore it receives only the latest page data). I changed your project a bit to make it work correctly. Take a look at her, hope this can be helpful.

EventFragment: replace getFragmentManager with getChildFragmentManager as you need an EventListFragment to handle the EventFragment fragment manager, not the activity fragment manager

 public class EventFragment extends Fragment { @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_event, container, false); // Get and set up viewpager final ViewPager viewPager = (ViewPager) view.findViewById(R.id.event_view_pager); EventFragmentAdapter eventFragmentAdapter = new EventFragmentAdapter(getChildFragmentManager(), getEventDates()); viewPager.setAdapter(eventFragmentAdapter); // Get and set up tablayout final TabLayout tabLayout = (TabLayout) view.findViewById(R.id.event_tabs); tabLayout.setTabGravity(TabLayout.GRAVITY_FILL); tabLayout.post(new Runnable() { @Override public void run() { tabLayout.setupWithViewPager(viewPager); } }); return view; } /** * Retrieves the event dates for the hackathon so that the proper events can be displayed. * @return */ private List<LocalDate> getEventDates(){ List<LocalDate> eventDates = new ArrayList<>(); eventDates.add(new LocalDate(2015, 10, 17)); eventDates.add(new LocalDate(2015, 10, 18)); return eventDates; } } 

EventListFragment - I changed the query since sqlite query does not work with my locale (Italian)

 public class EventListFragment extends Fragment{ private EventAdapter mEventAdapter; private static final String TAG = EventListFragment.class.getSimpleName(); private static final String DATE_ARG = "eventDate"; public static EventListFragment newInstance(LocalDate date){ EventListFragment eventListFragment = new EventListFragment(); Bundle args = new Bundle(); args.putSerializable(DATE_ARG, date); eventListFragment.setArguments(args); return eventListFragment; } @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_event_list, container, false); // Setup recyclerview RecyclerView eventRecyclerView = (RecyclerView) view.findViewById(R.id.event_recycler_view); LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity()); layoutManager.setOrientation(LinearLayoutManager.VERTICAL); eventRecyclerView.setLayoutManager(layoutManager); // Get date LocalDate eventDate = (LocalDate) getArguments().getSerializable(DATE_ARG); // Set adapter mEventAdapter = new EventAdapter(getEvents(eventDate)); eventRecyclerView.setAdapter(mEventAdapter); Log.v(TAG, eventRecyclerView.toString()); return view; } /** * Retrieves the events for the given date for the fragment. */ private List<Event> getEvents(LocalDate date){ List<Event> returnList = new ArrayList<>(); String dateString = Utility.getDBDateString(date); List<String> dateList = new ArrayList<>(); Cursor cursor = getActivity().getContentResolver().query( GHContract.EventEntry.CONTENT_URI, new String[]{ "*", "substr(" + GHContract.EventEntry.COLUMN_TIME + ",0,11)" }, "substr(" + GHContract.EventEntry.COLUMN_TIME + ",0,11) = ? ", new String[]{dateString}, GHContract.EventEntry.COLUMN_TIME ); while(cursor.moveToNext()){ returnList.add(new Event(cursor)); dateList.add(cursor.getString(cursor.getColumnIndex(GHContract.EventEntry.COLUMN_TIME))); } cursor.close(); return returnList; } } 

EventAdapter - removed the static keyword and the link to the activity context (you need to get the context somewhere else)

 public class EventAdapter extends RecyclerView.Adapter<EventAdapter.ViewHolder> { private List<Event> mEvents; public EventAdapter(List<Event> events){ mEvents = events; } /** * Inflates the view for Event items. */ @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_event, parent, false); return new ViewHolder(view); } /** * Binds the data for an event to its view. */ @Override public void onBindViewHolder(ViewHolder holder, int position) { Event event = mEvents.get(position); holder.timeView.setText(Utility.getTimeString(event.getTime())); holder.titleView.setText(event.getTitle()); holder.locationView.setText(event.getLocation()); // If reminder time is not null, show check mark. If it is, show plus. if(event.getReminderTime() != null){ holder.alarmView.setImageDrawable(holder.itemView.getResources().getDrawable(R.drawable.ic_alarm_on)); } else{ holder.alarmView.setImageDrawable(holder.itemView.getResources().getDrawable(R.drawable.ic_add_alarm)); } } /** * Returns the size of the adapter. */ @Override public int getItemCount() { return mEvents.size(); } /** * Retains a reference to the view so `findViewById` calls are only made once for the adapter. */ public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{ public final TextView timeView; public final TextView titleView; public final TextView locationView; public final ImageView alarmView; public ViewHolder(View view){ super(view); timeView = (TextView) view.findViewById(R.id.event_time); titleView = (TextView) view.findViewById(R.id.event_title); locationView = (TextView) view.findViewById(R.id.event_location); alarmView = (ImageView) view.findViewById(R.id.event_add_reminder); alarmView.setOnClickListener(this); } /** * Handles the click a user makes on the alarm image view. */ @Override public void onClick(View v) { OnEventReminderClickListener activity = (OnEventReminderClickListener) v.getContext(); activity.onEventReminderClicked(mEvents.get(getPosition())); } } /** * Interface to call back to the activity when an alarm is clicked for an event item. */ public interface OnEventReminderClickListener{ void onEventReminderClicked(Event event); } } 

And finally, the application /build.gradle, since you need to get the same version for all support libraries (recycler, card, etc.)

 apply plugin: 'com.android.application' android { compileSdkVersion 22 buildToolsVersion "22.0.1" defaultConfig { applicationId "com.adammcneilly.grizzhacks" minSdkVersion 15 targetSdkVersion 22 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:22.2.1' compile 'joda-time:joda-time:2.7' compile 'com.android.support:recyclerview-v7:22.2.1' compile 'com.android.support:cardview-v7:22.2.1' compile 'com.android.support:design:22.2.1' } 
+2
source

Could you try it?

 public class EventFragmentAdapter extends FragmentStatePagerAdapter { private List<LocalDate> mEventDates; private List<EventListFragment> mFragments; public EventFragmentAdapter(FragmentManager fragmentManager, List<LocalDate> eventDates){ super(fragmentManager); this.mEventDates = eventDates; this.mFragments = new ArrayList<>; for (LocalDate date : this.mEventDates) { this.mFragments.add(EventListFragment.newInstance(date)); } } @Override public Fragment getItem(int i) { return mFragments.get(i); } @Override public int getCount() { return mEventDates.size(); } @Override public CharSequence getPageTitle(int position) { return mEventDates.get(position).dayOfWeek().getAsText(); } } 

Then you check each element of mFragents if they have the expected content.

+1
source

All Articles