21

I have seen a few similar questions about onSaveInstanceState not getting called for Fragments, but in my case Fragments work fine, it's the main FragmentActivity that's having trouble.

The relevant code looks fairly simple:

public class MyFActivity extends FragmentActivity implements ActionBar.TabListener { 
    String[] allValues; // data to save

    @Override
    protected void onSaveInstanceState (Bundle outState) {
        Log.d("putting it!", allValues.toString());
        outState.putStringArray("allValues", allValues);
        super.onSaveInstanceState(outState);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (savedInstanceState != null) {
            allValues = savedInstanceState.getStringArray("allValues");
            Log.d("getting it!", allValues.toString());
        }
    }
}

When pausing the activity (using the back button), the onSaveInstanceState is never called, and consequently, savedInstanceState is always null within the onCreate method upon resuming the app. I tried adding a block like this:

@Override
public void onPause() {
    super.onPause();
    onSaveInstanceState(new Bundle());      
}

which was suggested in https://stackoverflow.com/a/14195202/362657 but while onSaveInstanceState then gets called, savedInstanceState remains null within onCreate method. What am I missing?

Community
  • 1
  • 1
SaltyNuts
  • 5,068
  • 8
  • 48
  • 80
  • 3
    "When pausing the activity (using the back button)" usually this destroys the Fragment so `savedInstanceState` is lost. Do you see the appropriate `saveInstanceState` in `onCreate()` when you rotate the device? – Sam Apr 10 '13 at 20:10
  • Rather than onCreate, perhaps you should be using onResume()? If the activity is only paused, then onCreate() should not get called again, onResume() should – Tom Apr 10 '13 at 20:11
  • Hm, that explains it! Destroyed it is. – SaltyNuts Apr 10 '13 at 20:16

3 Answers3

87

The issue here is that you are misunderstanding how onSaveInstanceState works. It is designed to save the state of the Activity/Fragment in the case that the OS needs to destroy it for memory reasons or configuration changes. This state is then passed back in onCreate when the Activity/Fragment is returned to / restarted.

In a Fragment, all of their lifecycle callbacks are directly tied to their parent Activity. So onSaveInstanceState gets called on the Fragment when its parent Activity has onSaveInstanceState called.

When pausing the activity (using the back button), the onSaveInstanceState is never called, and consequently, savedInstanceState is always null within the onCreate method upon resuming the app.

When pressing back, the user is destroying the Activity, and therefore its children Fragments, so there is no reason to call onSaveInstanceState, since the instance is being destroyed. When you reopen the Activity, it's a brand new instance, with no saved state, so the Bundle passed in onCreate is null. This is behaving exactly as designed. However, try rotating the device or hitting the home button, then you will see the Activity and its children Fragments have onSaveInstanceState called, and passed back in onCreate when returned to.

The hack you added, directly calling onSaveInstanceState(new Bundle()); inside of onPause, is a very bad practice, as you should never call the lifecycle callbacks directly. Doing so can put your app into illegal states.

If what you really want is for your data to persist beyond an instance of your app, I suggest you look into using SharedPreferences or databases for more advanced data. You can then save your persistent data in onPause() or whenever it changes.

starball
  • 20,030
  • 7
  • 43
  • 238
Steven Byle
  • 13,149
  • 4
  • 45
  • 57
  • Thank you for the thorough explanation. I was under the mistaken impression that the back button puts the activity in the background, not destroying it. – SaltyNuts Apr 10 '13 at 20:36
  • You are very welcome, it's a tricky concept, and isn't documented as well as I would like to see. – Steven Byle Apr 10 '13 at 20:38
  • @dymmeh that's awesome, +1 to you sir – Steven Byle Apr 10 '13 at 20:39
  • Great explanation. I wasn't sure why onSaveInstanceState wasn't getting called in my fragment. Now I know. Thanks – speedynomads Sep 13 '13 at 15:07
  • 2
    `In a Fragment, all of their lifecycle callbacks are directly tied to their parent Activity. So onSaveInstanceState gets called on the Fragment when its parent Activity has onSaveInstanceState called.` So what about fragments which are hosted by one single activity? Theoretically their such methods like `onSavedInstanceState` and ... will never get called as they are transited in the same activity, right? – inverted_index Oct 15 '17 at 08:44
  • @inverted_index if you are just *replacing* `Fragments` in a single `Activity`, `onSavedInstanceState` will not be called because the `Fragment` being replaced is being tossed out and doesn't need to save any state. However, I *believe* (you may have to test this) if you use a `Fragment` backstack, the OS will call `onSavedInstanceState` on the stacked `Fragments` when/if it needs to. And yes, if the parent `Activity` needs to call `onSavedInstanceState` it will also call it on its children `Fragments` first. – Steven Byle Oct 17 '17 at 18:02
10

In an update to the accepted answer:

A fragment's onSaveInstanceState may be called if you are using a ViewPager with a FragmentStatePagerAdapter (rather than FragmentPagerAdapter)

FragmentStatePagerAdapter

This version of the pager is more useful when there are a large number of pages, working more like a list view. When pages are not visible to the user, their entire fragment may be destroyed, only keeping the saved state of that fragment. This allows the pager to hold on to much less memory associated with each visited page as compared to FragmentPagerAdapter at the cost of potentially more overhead when switching between pages.

And don't forget:

When using FragmentPagerAdapter the host ViewPager must have a valid ID set.

Tanner Perrien
  • 3,133
  • 1
  • 28
  • 35
0

Not an accurate answer to the question, but may help someone's day. In my case, I called

@Override
public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
  super.onSaveInstanceState(outState, outPersistentState);
}

I replaced the above code as below and things worked

@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
    super.onSaveInstanceState(outState);
}
Ebin Joy
  • 2,690
  • 5
  • 26
  • 39