I have found many instances of a similar question on SO but no answer unfortunately meets my requirements.

I have different layouts for portrait and landscape and I am using back stack, which both prevents me from using setRetainState() and tricks using configuration change routines.

I show certain information to the user in TextViews, which do not get saved in the default handler. When writing my application solely using Activities, the following worked well:

TextView vstup;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.whatever);
    vstup = (TextView)findViewById(R.id.whatever);
    /* (...) */
}

@Override
public void onSaveInstanceState(Bundle state) {
    super.onSaveInstanceState(state);
    state.putCharSequence(App.VSTUP, vstup.getText());
}

@Override
public void onRestoreInstanceState(Bundle state) {
    super.onRestoreInstanceState(state);
    vstup.setText(state.getCharSequence(App.VSTUP));
}

With Fragments, this works only in very specific situations. Specifically, what breaks horribly is replacing a fragment, putting it in the back stack and then rotating the screen while the new fragment is shown. From what I understood, the old fragment does not receive a call to onSaveInstanceState() when being replaced but stays somehow linked to the Activity and this method is called later when its View does not exist anymore, so looking for any of my TextViews results into a NullPointerException.

Also, I found that keeping the reference to my TextViews is not a good idea with Fragments, even if it was OK with Activity's. In that case, onSaveInstanceState() actually saves the state but the problem reappears if I rotate the screen twice when the fragment is hidden, as its onCreateView() does not get called in the new instance.

I thought of saving the state in onDestroyView() into some Bundle-type class member element (it's actually more data, not just one TextView) and saving that in onSaveInstanceState() but there are other drawbacks. Primarily, if the fragment is currently shown, the order of calling the two functions is reversed, so I'd need to account for two different situations. There must be a cleaner and correct solution!

upvote
  flag
Here is very good example with detail explanation as well. emuneee.com/blog/2013/01/07/saving-fragment-states – Hesam
upvote
  flag
I second the emunee.com link. It solved a stick UI problem for me! – Reenactor Rob

6 Answers 11

This is the way I am using at this moment... it's very complicated but at least it handles all the possible situations. In case anyone is interested.

public final class MyFragment extends Fragment {
    private TextView vstup;
    private Bundle savedState = null;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.whatever, null);
        vstup = (TextView)v.findViewById(R.id.whatever);

        /* (...) */

        /* If the Fragment was destroyed inbetween (screen rotation), we need to recover the savedState first */
        /* However, if it was not, it stays in the instance from the last onDestroyView() and we don't want to overwrite it */
        if(savedInstanceState != null && savedState == null) {
            savedState = savedInstanceState.getBundle(App.STAV);
        }
        if(savedState != null) {
            vstup.setText(savedState.getCharSequence(App.VSTUP));
        }
        savedState = null;

        return v;
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        savedState = saveState(); /* vstup defined here for sure */
        vstup = null;
    }

    private Bundle saveState() { /* called either from onDestroyView() or onSaveInstanceState() */
        Bundle state = new Bundle();
        state.putCharSequence(App.VSTUP, vstup.getText());
        return state;
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        /* If onDestroyView() is called first, we can use the previously savedState but we can't call saveState() anymore */
        /* If onSaveInstanceState() is called first, we don't have savedState, so we need to call saveState() */
        /* => (?:) operator inevitable! */
        outState.putBundle(App.STAV, (savedState != null) ? savedState : saveState());
    }

    /* (...) */

}

Alternatively, it is always a possibility to keep the data displayed in passive Views in variables and using the Views only for displaying them, keeping the two things in sync. I don't consider the last part very clean, though.

57 upvote
  flag
This is the best solution I've found so far but there is still one (somewhat exotic) problem remaining: if you have two fragments, A and B, where A is currently on the backstack and B is visible, then you lose the state of A (the invisible one) if you rotate the display twice. The problem is that onCreateView() does not get called in this scenario, only onCreate(). So later, in onSaveInstanceState() there are no views to save the state from. One would have to store and then save the state passed in onCreate(). – devconsole
6 upvote
  flag
@devconsole I wish I could give you 5 up votes for this comment! This rotation twice thing has been killing me for days. – DroidT
upvote
  flag
Thank you for the great answer! I have one question though. Where is the best place to instantiate model object (POJO) in this fragment? – Renjith
upvote
  flag
@devconsole your comment saved my day..It is more helpful than all the answers posted for the question..Thanks a lot!! – Droidwala
4 upvote
  flag
To help save time for others, App.VSTUP and App.STAV are both string tags that represent the objects they are trying to obtain. Example: savedState = savedInstanceState.getBundle(savedGamePlayString); or savedState.getDouble("averageTime") – Tanner Hallman
upvote
  flag
This is a thing of beauty. – Ivan
up vote 399 down vote accepted

To correctly save the instance state of Fragment you should do the following:

1. In the fragment, save instance state by overriding onSaveInstanceState() and restore in onActivityCreated():

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    ...
    if (savedInstanceState != null) {
        //Restore the fragment's state here
    }
}
...
@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);

    //Save the fragment's state here
}

2. And important point, in the activity, you have to save the fragment's instance in onSaveInstanceState() and restore in onCreate().

public void onCreate(Bundle savedInstanceState) {
    ...
    if (savedInstanceState != null) {
        //Restore the fragment's instance
        mContent = getSupportFragmentManager().getFragment(savedInstanceState, "myFragmentName");
        ...
    }
    ...
}

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);

    //Save the fragment's instance
    getSupportFragmentManager().putFragment(outState, "myFragmentName", mContent);
}

Hope this helps.

5 upvote
  flag
This worked perfectly for me! No workarounds, no hacks, just makes sense this way. Thank you for this, made hours of searching successful. SaveInstanceState() of your values in fragment, then save fragment in Activity holding the fragment, then restore :) – MattMatt
47 upvote
  flag
What is mContent? – wizurd
9 upvote
  flag
@wizurd mContent is a Fragment, it's reference to the instance of the current fragment in the activity. – ThanhHH
upvote
  flag
if you have fragments that may or may not be present (created, destroyed, or replaced by user actions) you can in Activity's onCreate, if(savedInstanceState != null) do this: if(savedInstanceState.containsKey(FRAG_TAG))){ frag1 = (FragOne)getFragmentManager().getFragment(savedInstanceState‌​, FRAG_TAG); – ross studtman
4 upvote
  flag
Probably have to cast return from getFragment() ie, mContent = (ContentFragment)getSupportFragmentManager().getFragment(sav‌​edInstanceState, "mContent"); – ross studtman
1 upvote
  flag
After orientation change it is not storing fragments which is in back stack – kavie
8 upvote
  flag
Can you explain how this will save the instance state of a fragment in back stack? That's what the OP asked. – hitmaneidos
1 upvote
  flag
Can somebody explain how is this answer related to saving state of fragment which is in the backstack? – user2203031
24 upvote
  flag
This is not related to question, onSaveInstance is not called when fragment is putted to backstack – Tadas Valaitis
3 upvote
  flag
Why we need to do work in activity's onSaveInstanceState and onCreate? Will call to super in fragment will not do it automatically. – Akhil Dad
2 upvote
  flag
@ThanhHH after restoring fragment instance in mContent, where is mContent used further? – Kushal
upvote
  flag
Shouldn't you call super.onSaveInstanceState(outState); after you update the outstate with your arguments to store? – MikeL
upvote
  flag
This is not required if you create the fragment correctly. The fragment should be created when the savedInstanceState is null. otherwise, you will restore the fragment, but it will be replaced with a brand new fragment – Avinash R
1 upvote
  flag
only 2nd point is good enough to save the fragment state. – Kaveesh Kanwal
1 upvote
  flag
Thank you a thousand times for mentioning putFragment and getFragment. Those methods are not famous enough yet! – avalancha
10 upvote
  flag
But what if you're using this in a viewpager.. – Oliver Dixon
1 upvote
  flag
I think the fragment's vars should be restored in onCreate and not in onActivityCreated. In fact there are some cases when onActivityCreated is not called (i.e. when fragment is restored but in BackStack) but you need your vars correctly restored. – valfer
upvote
  flag
Does not setRetainInstance(true) save fragments instance state? Is there a need to call onSaveInstanceState for fragments? – Sermilion
upvote
  flag
Is there a reason to use Activity's onCreate() rather than onRestoreInstanceState()? – Marcel Bro
upvote
  flag
I don't think you need #2 for saving a basic piece of data in a Fragment. Refer to link inthecheesefactory.com/blog/… – Adam Hurwitz
upvote
  flag

I just want to give the solution that I came up with that handles all cases presented in this post that I derived from Vasek and devconsole. This solution also handles the special case when the phone is rotated more than once while fragments aren't visible.

Here is were I store the bundle for later use since onCreate and onSaveInstanceState are the only calls that are made when the fragment isn't visible

MyObject myObject;
private Bundle savedState = null;
private boolean createdStateInDestroyView;
private static final String SAVED_BUNDLE_TAG = "saved_bundle";

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (savedInstanceState != null) {
        savedState = savedInstanceState.getBundle(SAVED_BUNDLE_TAG);
    }
}

Since destroyView isn't called in the special rotation situation we can be certain that if it creates the state we should use it.

@Override
public void onDestroyView() {
    super.onDestroyView();
    savedState = saveState();
    createdStateInDestroyView = true;
    myObject = null;
}

This part would be the same.

private Bundle saveState() { 
    Bundle state = new Bundle();
    state.putSerializable(SAVED_BUNDLE_TAG, myObject);
    return state;
}

Now here is the tricky part. In my onActivityCreated method I instantiate the "myObject" variable but the rotation happens onActivity and onCreateView don't get called. Therefor, myObject will be null in this situation when the orientation rotates more than once. I get around this by reusing the same bundle that was saved in onCreate as the out going bundle.

    @Override
public void onSaveInstanceState(Bundle outState) {

    if (myObject == null) {
        outState.putBundle(SAVED_BUNDLE_TAG, savedState);
    } else {
        outState.putBundle(SAVED_BUNDLE_TAG, createdStateInDestroyView ? savedState : saveState());
    }
    createdStateInDestroyView = false;
    super.onSaveInstanceState(outState);
}

Now wherever you want to restore the state just use the savedState bundle

  @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    ...
    if(savedState != null) {
        myObject = (MyObject) savedState.getSerializable(SAVED_BUNDLE_TAG);
    }
    ...
}
upvote
  flag
Can you tell me...What is "MyObject " here? – kavie
2 upvote
  flag
Anything you want it to be. It is just an example representing something that would be saved in the bundle. – DroidT

On the latest support library none of the solutions discussed here are necessary anymore. You can play with your Activity's fragments as you like using the FragmentTransaction. Just make sure that your fragments can be identified either with an id or tag.

The fragments will be restored automatically as long as you don't try to recreate them on every call to onCreate(). Instead, you should check if savedInstanceState is not null and find the old references to the created fragments in this case.

Here is an example:

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

    if (savedInstanceState == null) {
        myFragment = MyFragment.newInstance();
        getSupportFragmentManager()
                .beginTransaction()
                .add(R.id.my_container, myFragment, MY_FRAGMENT_TAG)
                .commit();
    } else {
        myFragment = (MyFragment) getSupportFragmentManager()
                .findFragmentByTag(MY_FRAGMENT_TAG);
    }
...
}

Note however that there is currently a bug when restoring the hidden state of a fragment. If you are hiding fragments in your activity, you will need to restore this state manually in this case.

2 upvote
  flag
Is this fix something you noticed while using the support library or did you read about it somewhere? Is there any more information you could provide about it? Thanks! – Piovezan
1 upvote
  flag
@Piovezan it can be sort of implicitly inferred from the docs. For example, the beginTransaction() doc reads as follow: "This is because the framework takes care of saving your current fragments in the state (...)" . I have also been coding my apps with this expected behavior for quite some time now. – Ricardo Lage
1 upvote
  flag
@Ricardo does this apply if using a ViewPager? – Derek Beattie
1 upvote
  flag
Normally yes, unless you changed the default behavior on your implementation of FragmentPagerAdapter or FragmentStatePagerAdapter. If you look at the code of FragmentStatePagerAdapter, for example, you will see that the restoreState() method restores the fragments from the FragmentManager you passed as parameter when creating the adapter. – Ricardo Lage
upvote
  flag
Could you specify which version mentioned above? – Robert
3 upvote
  flag
I think this contribution is the best answer to the original question. It's also the one that - in my opinion - is best aligned with how the Android platform works. I would recommend marking this answer as the "Accepted" one to better help future readers. – dbm
upvote
  flag
I agreed that this contribution should be the correct answer. But i need to add, that for this to work, you need to no override the onSaveInstanceState method of your activity or to call super.onSaveInstanceState in your implementation. – sonic

Thanks to DroidT, I made this:

I realize that if the Fragment does not execute onCreateView(), its view is not instantiated. So, if the fragment on back stack did not create its views, I save the last stored state, otherwise I build my own bundle with the data I want to save/restore.

1) Extend this class:

import android.os.Bundle;
import android.support.v4.app.Fragment;

public abstract class StatefulFragment extends Fragment {

    private Bundle savedState;
    private boolean saved;
    private static final String _FRAGMENT_STATE = "FRAGMENT_STATE";

    @Override
    public void onSaveInstanceState(Bundle state) {
        if (getView() == null) {
            state.putBundle(_FRAGMENT_STATE, savedState);
        } else {
            Bundle bundle = saved ? savedState : getStateToSave();

            state.putBundle(_FRAGMENT_STATE, bundle);
        }

        saved = false;

        super.onSaveInstanceState(state);
    }

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

        if (state != null) {
            savedState = state.getBundle(_FRAGMENT_STATE);
        }
    }

    @Override
    public void onDestroyView() {
        savedState = getStateToSave();
        saved = true;

        super.onDestroyView();
    }

    protected Bundle getSavedState() {
        return savedState;
    }

    protected abstract boolean hasSavedState();

    protected abstract Bundle getStateToSave();

}

2) In your Fragment, you must have this:

@Override
protected boolean hasSavedState() {
    Bundle state = getSavedState();

    if (state == null) {
        return false;
    }

    //restore your data here

    return true;
}

3) For example, you can call hasSavedState in onActivityCreated:

@Override
public void onActivityCreated(Bundle state) {
    super.onActivityCreated(state);

    if (hasSavedState()) {
        return;
    }

    //your code here
}
final FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.hide(currentFragment);
ft.add(R.id.content_frame, newFragment.newInstance(context), "Profile");
ft.addToBackStack(null);
ft.commit();

Not the answer you're looking for? Browse other questions tagged or ask your own question.