Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android Lifecycle management of Fragments within ViewPager and FragmentPagerAdapter

I have been struggling to find out what the correct management of Fragments within a FragmentActivity with a ViewPager is. Before I go into details, a quick summary of the issue that I am facing is the following:

I have a FragmentActivity with a ViewPager. The ViewPager uses a custom, yet very simple FragmentPagerAdapter. Each Fragment within the ViewPager comprises of an ExpandableListView. I also have an action bar button called "Refresh". For now, let's assume that the ViewPager has only one Fragment. The activity is created, and the Fragment's ExpandableListView is populated (so far so good). When the Refresh button is clicked, the handling method within the FragmentActivity iterates over the list of Fragments that are assigned to the FragmentPagerAdapter and calls refresh() on each Fragment to populate its ListView. However, when the orientation of the device changes (e.g. from portrait to landscape), the Activity is recreated and so are the fragments. Clicking the Refresh button now will iterate over non-initialised Fragments.

I know that I am being quite vague, especially without sample code, but please bear with me. I have traced the problem and method calls as follows from the start of the application/activity:

  1. FragmentActivity.onCreate()
  2. FragmentActivity.setContentView()
  3. FragmentActivity.createPagerFragments() <-- this creates an ArrayList of Fragments and assignes them to a new FragmentPagerAdapter which is in turn assigned to the ViewPager.
  4. Fragment.onAttach()
  5. Fragment.onCreate() <-- nothing special here, just calling the super method.
  6. Fragment.onCreateView() <-- nothing special here either, just inflating the layout
  7. Fragment.onActivityCreated() <-- nothing here either.
  8. << All good, orientation changes here >>
  9. FragmentActivity.onCreate()
  10. Fragment.onAttach()
  11. Fragment.onCreate()
  12. FragmentActivity.setContentView()
  13. FragmentActivity.createPagerFragments()
  14. Fragment.onCreateView()
  15. Fragment.onActivityCreated()
  16. << Refresh button clicked >>
  17. FragmentActivity.refresh() <-- iterates over the newly created Fragments from #13 (not these by Android!).
  18. << Crash: NullPointerException for mExpandableListView in Fragment. >>

So the problem, as I see it, is as follows: When Android re-creates the FragmentActivity and its Views after a change of screen orientation (calls #9-15 above), it creates new Fragment objects with their state restored to what the original ones were. However, these ones appear to be completely managed by the FragmentManager, and not by the FragmentPagerAdapter. In contrast, when the FragmentPagerAdapter is re-created along with the Fragments in the activity's onCreate method (see call #13) the Fragments that get assigned to the adapter never have their Fragment.onCreate() or Fragment.onCreateView() methods called at all. So when the refresh() method is called (see #17) the method iterates over these Fragments that have not been initialised. Therefore, when they try to populate the ExpandableListView, the view's instance variable is NULL. This is to be expected as the instance variable is only assigned in the Fragment.onCreateView() method that never gets called on these Fragments.

So my question is: how does one properly make re-use of the re-recreated (by Android) Fragments after the screen orientation has changed in order to avoid creating new ones that don't get initialised? I need to have a valid reference to them, in order to call their refresh() method that populates them on-demand. Ideally, they should also be assigned to the FragmentPagerAdapter as well.

I hope I have been clear in describing the issue, and the reason that I have not provided sample code is because the problem (as can be seen) is not from the code itself but from a rather incorrect (seemigly) re-creation of Fragments rather than re-use. But if needed, I can give you sample code, I just through this way would be clearer.

Thank you!

like image 865
vmivanov Avatar asked Dec 03 '13 15:12

vmivanov


2 Answers

It's lot to read, but after reading just introduction and the question and having experience with FragmentStatePagerAdapter, which is similar to FragmentPagerAdapter I can tell you that:

After rotation your adapter will AUTOMAGICALLY attach old fragments. So it seems that although activity creating adapter is being recreated, FragmentManager, which is global and it's instance preserve activity's recreation will detect that new FragmentStatePagerAdapter is combined with the same ViewPager and is asking for the same Fragments and will simply fetch them from Fragment's BackStack.

You as designer of Fragments can notice this behavior by continues invocation of Fragment.onAttach() and Fragment.onDetach(). When onAttach() occurs it's either creation of your Fragment or reusing it after rotation. You should be able to distinguish that Fragment was rotated with use of callback onRestoreRnstanceState().

You will see in your logs many onCreate() and other states logs simultaneously, because FragmentStatePagerAdapter always fetches/creates min 3 Fragments (except if you set that they are only 2 or 1), so also after screen rotation 3 fragments will be reattached from backstack.

I hope that it helped.

like image 79
Malachiasz Avatar answered Sep 25 '22 19:09

Malachiasz


I believe that this question, about retrieving the current fragment from a ViewPager, will help you. As already pointed out, fragments are managed by the Fragment(State)PagerAdapter and NOT Activity's or Fragment's lifecycle.

  • The first time the activity is created, fragments are returned by the getItem method. This method is called only once per fragment, even if the activity gets recreated.
  • Subsequent times, the fragments are returned by the instantiateItem method. Most probably, this is the place, where you need to get hold of your fragments and call their refresh methods.
like image 37
kgiannakakis Avatar answered Sep 25 '22 19:09

kgiannakakis