Fragment recreated every time after orientation change, unable to restore state
Asked Answered
N

5

8

Update: It turns out that the problem is from elsewhere. Thanks @Luksprog for pointing out what I overlooked.

  1. The project is created using Android Studio's navigation drawer pattern. The drawer is implemented in the NavigationDrawerFragment class.
  2. The fragment holding the view pager is added when a particular item in the drawer is selected. The code is implemented my home activity.
  3. When screen rotates, the onCreate() method of NavigationDrawerFragment is called, preserving last selected item.
  4. And here is what went wrong - upon recreation, NavigationDrawerFragment will call selectItem() again, which triggers my menu item selected handler. This causes the ListFragment restored by Android.

This can be prevented by checking the active menu item in my menu selection handler code.


I want to retain the last viewing page index of the ViewPager when the activity is recreated by whatever reason, e.g. orientation change.

The ViewPager is in a Fragment (named ListFragment), which is attached to an activity. I am using the compat library, so the fragment is a subclass of android.support.v4.app.Fragment.

I thought that it could be done by overriding the onSaveInstanceState() method and add appropriate logic in onCreate(), as metioned in the doc:

To properly handle a restart, it is important that your activity restores its previous state through the normal Activity lifecycle, in which Android calls onSaveInstanceState() before it destroys your activity so that you can save data about the application state. You can then restore the state during onCreate() or onRestoreInstanceState().

But the situation seems different for fragments. The page index can be correctly restored when I navigate from this ListFragment to another activity and pressed "back". However when I rotate my device, the page index is lost.

I added some logging to see what's wrong. From the log I found that although onSaveInstanceState() of the ListFragment(I'll call it ListFragment A) is called properly, this particular Fragment class is no longer shown in the activity. When the orientation changed and the activity is recreated, Android calls onSaveInstanceState() followed by onDetach() to detach this fragment. Then Android creates a new instance of ListFragment (I'll call it ListFragment B) and attach it to the new, rotated activity. This ListFragment B has an empty savedInstanceState passed to the constructor, and thus the last page index (and any configuration in savedInstanceState of Fragment A) is lost.

In fact, a new instance of ListFragment will be created every time a screen rotate occurs, but it seems that the old ones will not be destroyed. I see logs like below when I rotate the device:

D/ListFragment﹕ [1110257048] onSaveInstanceState() called, storing last page index 3
D/ListFragment﹕ [1109835992] onSaveInstanceState() called, storing last page index 0
D/ListFragment﹕ [1108826176] onSaveInstanceState() called, storing last page index 0
D/ListFragment﹕ [1108083096] onSaveInstanceState() called, storing last page index 0
D/ListFragment﹕ [1106541040] onSaveInstanceState() called, storing last page index 0
D/ListFragment﹕ [1108316656] onSaveInstanceState() called, storing last page index 0
D/ListFragment﹕ [1109134136] onSaveInstanceState() called, storing last page index 0
D/ListFragment﹕ [1108630992] onSaveInstanceState() called, storing last page index 0
D/ListFragment﹕ [1108592888] onSaveInstanceState() called, storing last page index 0
D/ListFragment﹕ [1109729064] onSaveInstanceState() called, storing last page index 0
D/ListFragment﹕ [1110257048] onDestroy()
D/ListFragment﹕ [1110257048] onDetach()
D/ListFragment﹕ [1109835992] onDestroy()
D/ListFragment﹕ [1109835992] onDetach()
D/ListFragment﹕ [1108826176] onDestroy()
D/ListFragment﹕ [1108826176] onDetach()
D/ListFragment﹕ [1108083096] onDestroy()
D/ListFragment﹕ [1108083096] onDetach()
D/ListFragment﹕ [1106541040] onDestroy()
D/ListFragment﹕ [1106541040] onDetach()
D/ListFragment﹕ [1108316656] onDestroy()
D/ListFragment﹕ [1108316656] onDetach()
D/ListFragment﹕ [1109134136] onDestroy()
D/ListFragment﹕ [1109134136] onDetach()
D/ListFragment﹕ [1108630992] onDestroy()
D/ListFragment﹕ [1108630992] onDetach()
D/ListFragment﹕ [1108592888] onDestroy()
D/ListFragment﹕ [1108592888] onDetach()
D/ListFragment﹕ [1109729064] onDestroy()
D/ListFragment﹕ [1109729064] onDetach()
D/ListFragment﹕ [1110903656] onAttach()
D/ListFragment﹕ [1110903656] onCreate()
D/ListFragment﹕ [1110903656] savedInstanceState is not NULL.
D/ListFragment﹕ [1110903656] Retrieving last page index 3
D/ListFragment﹕ [1110905248] onAttach()
D/ListFragment﹕ [1110905248] onCreate()
D/ListFragment﹕ [1110905248]   savedInstanceState is not NULL.
D/ListFragment﹕ [1110905248]   Retrieving last page index 0
D/ListFragment﹕ [1110906440] onAttach()
D/ListFragment﹕ [1110906440] onCreate()
D/ListFragment﹕ [1110906440]   savedInstanceState is not NULL.
D/ListFragment﹕ [1110906440]   Retrieving last page index 0
D/ListFragment﹕ [1110907632] onAttach()
D/ListFragment﹕ [1110907632] onCreate()
D/ListFragment﹕ [1110907632]   savedInstanceState is not NULL.
D/ListFragment﹕ [1110907632]   Retrieving last page index 0
D/ListFragment﹕ [1110908824] onAttach()
D/ListFragment﹕ [1110908824] onCreate()
D/ListFragment﹕ [1110908824]   savedInstanceState is not NULL.
D/ListFragment﹕ [1110908824]   Retrieving last page index 0
D/ListFragment﹕ [1110910016] onAttach()
D/ListFragment﹕ [1110910016] onCreate()
D/ListFragment﹕ [1110910016]   savedInstanceState is not NULL.
D/ListFragment﹕ [1110910016]   Retrieving last page index 0
D/ListFragment﹕ [1110911208] onAttach()
D/ListFragment﹕ [1110911208] onCreate()
D/ListFragment﹕ [1110911208]   savedInstanceState is not NULL.
D/ListFragment﹕ [1110911208]   Retrieving last page index 0
D/ListFragment﹕ [1110912400] onAttach()
D/ListFragment﹕ [1110912400] onCreate()
D/ListFragment﹕ [1110912400]   savedInstanceState is not NULL.
D/ListFragment﹕ [1110912400]   Retrieving last page index 0
D/ListFragment﹕ [1110913592] onAttach()
D/ListFragment﹕ [1110913592] onCreate()
D/ListFragment﹕ [1110913592]   savedInstanceState is not NULL.
D/ListFragment﹕ [1110913592]   Retrieving last page index 0
D/ListFragment﹕ [1110914784] onAttach()
D/ListFragment﹕ [1110914784] onCreate()
D/ListFragment﹕ [1110914784]   savedInstanceState is not NULL.
D/ListFragment﹕ [1110914784]   Retrieving last page index 0
D/HomeActivity﹕ fragment updated
D/ListFragment﹕ [1110914784] onCreateView()
D/ListFragment﹕ [1111031048] onAttach()
D/HomeActivity﹕ Fragment attached.
D/ListFragment﹕ [1111031048] onCreate()
D/ListFragment﹕ [1111031048]   savedInstanceState is NULL.
D/ListFragment﹕ [1111031048] onCreateView()
D/ListFragment﹕ [1111031048] onResume(), restoring page index 0

This is the log after I rotated the screen for about 10 times. The number in the tag is the classes' hashCode(). Above lines shows that onSaveInstanceState() and onCreate() of the previously created fragments still get called even after they are replaced by the latest (1111031048) one.

Note that I didn't call setRetainInstance() in the fragment class. In fact, I tried both setRetainInstance(false) and setRetainInstance(true) but it doesn't change anything.

Did I do anything wrong here? I can understand that ListFragment needs to be recreated, but why savedInstanceState is null? And if this is the expected behavior, what is the correct way to solve my need, i.e. keeping the page index when configuration changes?

It should be possible to make the page index a static class variable, but I'm not sure if it is actually solving the issue, or just hiding it (because I smell memory leak in the log above).

Nowlin answered 24/1, 2014 at 14:9 Comment(5)
Do you call super on all overrided activity and fragment methods (like onCreate, onSaveInstanceState) and so on?Feeler
Are you sure your fragment which holds the ViewPager isn't added again(by mistake) by your code after the rotation, replacing the saved one?Garber
@Luksprog You are absolutely correct. I am too focused on the fragment, and overlooked this part. I will update my post. Thank you very much!Nowlin
You should post an answer and accept it(for your other questions as well if you got a proper answer) so the question will appear as solved.Garber
Thanks. I was not sure if it's appropriate for me to answer my own question (and mark it as an answer) for stupid mistake like this, but I followed your advice and hope it may soon help someone.Nowlin
N
5

As updated in the question, this is resolved and thanks again to @Luksprog for pointing out what I overlooked.

The behavior of Fragment actually aligns with Activity classes.

Here is the cause of my issue:

  • The project is created using navigation drawer pattern provided by Android Studio's "create project" wizard. The drawer is implemented in the NavigationDrawerFragment class.
  • The fragment holding the view pager is added when a particular item in the drawer is selected. The code is implemented my home activity.
  • When screen rotates, the onCreate() method of NavigationDrawerFragment is called, preserving last selected item.
  • And here is what went wrong - upon recreation, NavigationDrawerFragment will call selectItem() again, which triggers my menu item selected handler. This causes the ListFragment be replaced.

This can be prevented by checking the active menu item in my menu selection handler code, or by disabling that selectItem() call.

Nowlin answered 27/1, 2014 at 9:20 Comment(0)
S
10

Even though the answer has already been accepted, let me clarify this more: the "issue" is in the Android Studio template. The problem, as pointed out by Edmund, is in the Navigation Drawer calling the menu when recreated, thus re-calling the Fragment. To solve this, I made this slight modification on the NavigationDrawerFragment.java file as proposed by Android Studio. Original:

    @Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Read in the flag indicating whether or not the user has demonstrated awareness of the
    // drawer. See PREF_USER_LEARNED_DRAWER for details.
    SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity());
    mUserLearnedDrawer = sp.getBoolean(PREF_USER_LEARNED_DRAWER, false);

    if (savedInstanceState != null) {
        mCurrentSelectedPosition = savedInstanceState.getInt(STATE_SELECTED_POSITION);
        mFromSavedInstanceState = true;
    }

    // Select either the default item (0) or the last selected item.
    selectItem(mCurrentSelectedPosition);

}

New one:

    @Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Read in the flag indicating whether or not the user has demonstrated awareness of the
    // drawer. See PREF_USER_LEARNED_DRAWER for details.
    SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity());
    mUserLearnedDrawer = sp.getBoolean(PREF_USER_LEARNED_DRAWER, false);

    if (savedInstanceState != null) {
        mCurrentSelectedPosition = savedInstanceState.getInt(STATE_SELECTED_POSITION);
        mFromSavedInstanceState = true;
    } else {
        // Select either the default item (0) or the last selected item.
        selectItem(mCurrentSelectedPosition);
    }
}

Just lost half a production day to figure this out. Hope it may help somebody else.

Spicebush answered 23/10, 2014 at 7:36 Comment(2)
This helped me out a ton. I was trying to figure out how to prevent the recreation of the fragment on screen rotation. Thanks!Delicatessen
Brilliant! This is just what I was looking for. I wonder why pre-built navigation drawer is not fully prepared for configuration changes. It helped! Thanks!Peele
N
5

As updated in the question, this is resolved and thanks again to @Luksprog for pointing out what I overlooked.

The behavior of Fragment actually aligns with Activity classes.

Here is the cause of my issue:

  • The project is created using navigation drawer pattern provided by Android Studio's "create project" wizard. The drawer is implemented in the NavigationDrawerFragment class.
  • The fragment holding the view pager is added when a particular item in the drawer is selected. The code is implemented my home activity.
  • When screen rotates, the onCreate() method of NavigationDrawerFragment is called, preserving last selected item.
  • And here is what went wrong - upon recreation, NavigationDrawerFragment will call selectItem() again, which triggers my menu item selected handler. This causes the ListFragment be replaced.

This can be prevented by checking the active menu item in my menu selection handler code, or by disabling that selectItem() call.

Nowlin answered 27/1, 2014 at 9:20 Comment(0)
S
2

You dont need to handle on savedInstanceState. Because all fragments stored in fragment manger (it like array of fragments) and when the orientation changes activity destroys but fragment manager still hold all fragments that was added before. So you just need to check if fragment was already added.

private void openFragment(Fragment fragment, String tag) {
            FragmentManager fragmentManager = getSupportFragmentManager();
            FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
            Fragment existingFragment = fragmentManager.findFragmentByTag(tag);
            if (existingFragment != null) {
                fragmentTransaction.add(R.id.container, fragment, tag);
                fragmentTransaction.commit();
            }
        }
Stall answered 18/5, 2018 at 7:56 Comment(0)
D
0

Try using onCreateView() here instead of onCreate(), which is specific to Fragments and might make a difference.

Override onSaveInstanceState() to store your state, and recover it in onCreateView() using savedInstanceState.get*(). This is how we do it in our apps, and it should work. As @Ari mentioned, make sure to call super.onSaveInstanceState(outState).

class MyFragment extends ListFragment {

    @Override
    public void onSaveInstanceState(Bundle outState) {
        outState.putString(ARG_PAGE, page); 
        super.onSaveInstanceState(outState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        if(savedInstanceState != null){
            if(savedInstanceState.containsKey(ARG_PAGE)){
                page = savedInstanceState.getInteger(ARG_PAGE);
            }
        }
    }
}
Derman answered 24/1, 2014 at 14:46 Comment(0)
N
-2

In AndroidManifest.xml add

<activity android:name=".MainActivity"
android:configChanges="orientation|screenSize">
</activity>
Nett answered 3/2, 2016 at 10:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.