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 Fragment
s, 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 TextView
s results into a NullPointerException
.
Also, I found that keeping the reference to my TextViews
is not a good idea with Fragment
s, 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!
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.
Alternatively, it is always a possibility to keep the data displayed in passive
View
s in variables and using theView
s only for displaying them, keeping the two things in sync. I don't consider the last part very clean, though.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
Since destroyView isn't called in the special rotation situation we can be certain that if it creates the state we should use it.
This part would be the same.
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.
Now wherever you want to restore the state just use the savedState bundle
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:
2) In your Fragment, you must have this:
3) For example, you can call hasSavedState in onActivityCreated:
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 inonActivityCreated()
:2. And important point, in the activity, you have to save the fragment's instance in
onSaveInstanceState()
and restore inonCreate()
.Hope this helps.
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 theFragmentTransaction
. 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 ifsavedInstanceState
is not null and find the old references to the created fragments in this case.Here is an example:
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.