542

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!

Rafael Tavares
  • 5,678
  • 4
  • 32
  • 48
The Vee
  • 11,420
  • 5
  • 27
  • 60

7 Answers7

584

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():

class MyFragment extends Fragment {

    @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().

class MyActivity extends Activity {

    private MyFragment 

    public void onCreate(Bundle savedInstanceState) {
        ...
        if (savedInstanceState != null) {
            //Restore the fragment's instance
            mMyFragment = getSupportFragmentManager().getFragment(savedInstanceState, "myFragmentName");
            ...
        }
        ...
    }
    
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
            
        //Save the fragment's instance
        getSupportFragmentManager().putFragment(outState, "myFragmentName", mMyFragment);
    }

}
Halo
  • 1,730
  • 1
  • 8
  • 31
ThanhHH
  • 6,560
  • 1
  • 19
  • 22
  • 8
    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 Jan 31 '14 at 06:19
  • 14
    @wizurd mContent is a Fragment, it's reference to the instance of the current fragment in the activity. – ThanhHH Jul 30 '14 at 04:39
  • 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 Sep 08 '14 at 01:22
  • 6
    Probably have to cast return from `getFragment()` ie, `mContent = (ContentFragment)getSupportFragmentManager().getFragment(savedInstanceState, "mContent");` – ross studtman Sep 08 '14 at 19:41
  • 3
    After orientation change it is not storing fragments which is in back stack – kavie Oct 18 '14 at 10:14
  • 15
    Can you explain how this will save the instance state of a fragment in back stack? That's what the OP asked. – hitmaneidos Nov 05 '14 at 16:05
  • 3
    Can somebody explain how is this answer related to saving state of fragment which is in the backstack? – user2203031 Nov 21 '14 at 10:14
  • 64
    This is not related to question, onSaveInstance is not called when fragment is putted to backstack – Tadas Valaitis Jan 07 '15 at 10:14
  • 4
    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 May 08 '15 at 14:46
  • 3
    @ThanhHH after restoring fragment instance in `mContent`, where is `mContent` used further? – Kushal May 20 '15 at 12:34
  • Shouldn't you call super.onSaveInstanceState(outState); after you update the outstate with your arguments to store? – MikeL Jul 08 '15 at 21:10
  • 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 Jul 30 '15 at 12:06
  • 4
    Thank you a thousand times for mentioning `putFragment` and `getFragment`. Those methods are not famous enough yet! – avalancha Sep 16 '15 at 15:27
  • 4
    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 Nov 17 '16 at 09:10
  • Does not setRetainInstance(true) save fragments instance state? Is there a need to call onSaveInstanceState for fragments? – Sermilion Jan 21 '17 at 19:08
  • Is there a reason to use Activity's `onCreate()` rather than `onRestoreInstanceState()`? – Marcel Bro Feb 21 '17 at 16:50
  • I don't think you need #2 for saving a basic piece of data in a Fragment. Refer to link https://inthecheesefactory.com/blog/fragment-state-saving-best-practices/en – AdamHurwitz Aug 29 '17 at 02:22
  • When I do this, I get `java.lang.RuntimeException: Unable to start activity ComponentInfo{my.app/my.app.MyActivity}: java.lang.IllegalStateException: Fragment no longer exists for key myFragmentName: index 0`. How do I solve this? – Chris Watts Aug 17 '18 at 09:30
  • Why `onActivityCreated` and not `onCreate`? – EpicPandaForce Mar 31 '19 at 17:37
  • This does not make sense. The `onCreate` of the activity is not called if the app is still in the background. – Nimitz14 Oct 10 '19 at 13:48
  • What is mMyFragment? – Arron Lapta Nov 03 '22 at 01:10
92

This is a very old answer.

I don't write for Android anymore so function in recent versions is not guaranteed and there won't be any updates to it.

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.

The Vee
  • 11,420
  • 5
  • 27
  • 60
  • 72
    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 Jul 03 '13 at 13:55
  • 8
    @devconsole I wish I could give you 5 up votes for this comment! This rotation twice thing has been killing me for days. – DroidT Nov 02 '13 at 07:41
  • 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 Oct 28 '15 at 11:09
  • @devconsole your comment saved my day..It is more helpful than all the answers posted for the question..Thanks a lot!! – PunitD Dec 06 '15 at 15:09
  • 9
    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")` – hallmanitor Oct 22 '16 at 19:23
  • savedInstanceState is null i don't know why !! – zakaria Jan 29 '20 at 16:05
  • The main thing I didn't fully understand here is why you put one Bundle (`savedState`) inside another Bundle (`savedInstanceState`)? Is this because we do not know for sure, which is called first: `onDestroyView()` or `onSaveInstanceState()`? – soshial May 19 '20 at 17:56
  • Don't work. View v = inflater.inflate(R.layout.fragment, null) is wrong. I can't use this. In my case it's like View v = inflater.inflate(R.layout.fragment, container, false) I can't use null. – Nehemiah Narzary Jun 21 '21 at 16:09
67

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.

Ricardo
  • 7,785
  • 8
  • 40
  • 60
  • 2
    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 Feb 02 '15 at 21:38
  • 1
    @Piovezan it can be sort of implicitly inferred from the docs. For example, the [beginTransaction() doc](http://developer.android.com/reference/android/app/FragmentManager.html#beginTransaction()) 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 Mar 28 '15 at 23:32
  • 1
    @Ricardo does this apply if using a ViewPager? – Derek Beattie Nov 11 '15 at 01:45
  • 2
    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 Nov 11 '15 at 13:18
  • 1
    Could you specify which version mentioned above? – Robert Mar 11 '16 at 06:17
  • 5
    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 Mar 04 '17 at 08:01
  • 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 Aug 06 '17 at 14:36
  • What is newInstance()? This doesn't work. – Nehemiah Narzary Jun 22 '21 at 02:21
17

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);
    }
    ...
}
DroidT
  • 3,168
  • 3
  • 31
  • 29
5

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
}
The Guy with The Hat
  • 10,836
  • 8
  • 57
  • 75
Noturno
  • 121
  • 1
  • 8
0

Using this :

  private var mData: String? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        if (savedInstanceState != null) {
            mData= savedInstanceState.getString("Data")
        }
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        outState.putString("mData", Data)
    }
AmirMohamamd
  • 301
  • 3
  • 14
-9
final FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.hide(currentFragment);
ft.add(R.id.content_frame, newFragment.newInstance(context), "Profile");
ft.addToBackStack(null);
ft.commit();
Nilesh Savaliya
  • 666
  • 9
  • 9