I'm trying to save and restore the state of an Activity using the methods onSaveInstanceState() and onRestoreInstanceState().
The problem is that it never enters the onRestoreInstanceState() method. Can anyone explain to me why this is?
Usually you restore your state in onCreate(). It is possible to restore it in onRestoreInstanceState() as well, but not very common. (onRestoreInstanceState() is called after onStart(), whereas onCreate() is called before onStart().
Use the put methods to store values in onSaveInstanceState():
protected void onSaveInstanceState(Bundle icicle) {
super.onSaveInstanceState(icicle);
icicle.putLong("param", value);
}
And restore the values in onCreate():
public void onCreate(Bundle icicle) {
if (icicle != null){
value = icicle.getLong("param");
}
}
onRestoreInstanceState() is called only when recreating activity after it was killed by the OS. Such situation happen when:
orientation of the device changes (your activity is destroyed and recreated).
there is another activity in front of yours and at some point the OS kills your activity in order to free memory (for example). Next time when you start your activity onRestoreInstanceState() will be called.
In contrast: if you are in your activity and you hit Back button on the device, your activity is finish()ed (i.e. think of it as exiting desktop application) and next time you start your app it is started "fresh", i.e. without saved state because you intentionally exited it when you hit Back.
Other source of confusion is that when an app loses focus to another app onSaveInstanceState() is called but when you navigate back to your app onRestoreInstanceState() may not be called. This is the case described in the original question, i.e. if your activity was NOT killed during the period when other activity was in front onRestoreInstanceState() will NOT be called because your activity is pretty much "alive".
All in all, as stated in the documentation for onRestoreInstanceState():
Most implementations will simply use onCreate(Bundle) to restore their
state, but it is sometimes convenient to do it here after all of the
initialization has been done or to allow subclasses to decide whether
to use your default implementation. The default implementation of this
method performs a restore of any view state that had previously been
frozen by onSaveInstanceState(Bundle).
As I read it: There is no reason to override onRestoreInstanceState() unless you are subclassing Activity and it is expected that someone will subclass your subclass.
The state you save at onSaveInstanceState() is later available at onCreate() method invocation. So use onCreate (and its Bundle parameter) to restore state of your activity.
From the documentation Restore activity UI state using saved instance state it is stated as:
Instead of restoring the state during onCreate() you may choose to
implement onRestoreInstanceState(), which the system calls after the
onStart() method. The system calls onRestoreInstanceState() only if
there is a saved state to restore, so you do not need to check whether
the Bundle is null:
IMO, this is more clear way than checking this at onCreate, and better fits with single responsiblity principle.
As a workaround, you could store a bundle with the data you want to maintain in the Intent you use to start activity A.
Intent intent = new Intent(this, ActivityA.class);
intent.putExtra("bundle", theBundledData);
startActivity(intent);
Activity A would have to pass this back to Activity B. You would retrieve the intent in Activity B's onCreate method.
Intent intent = getIntent();
Bundle intentBundle;
if (intent != null)
intentBundle = intent.getBundleExtra("bundle");
// Do something with the data.
Another idea is to create a repository class to store activity state and have each of your activities reference that class (possible using a singleton structure.) Though, doing so is probably more trouble than it's worth.
The main thing is that if you don't store in onSaveInstanceState() then onRestoreInstanceState() will not be called. This is the main difference between restoreInstanceState() and onCreate(). Make sure you really store something. Most likely this is your problem.
I found that onSaveInstanceState is always called when another Activity comes to the foreground. And so is onStop.
However, onRestoreInstanceState was called only when onCreate and onStart were also called. And, onCreate and onStart were NOT always called.
So it seems like Android doesn't always delete the state information even if the Activity moves to the background. However, it calls the lifecycle methods to save state just to be safe. Thus, if the state is not deleted, then Android doesn't call the lifecycle methods to restore state as they are not needed.
Figure 2 describes this.
I think this thread was quite old. I just mention another case, that onSaveInstanceState() will also be called, is when you call Activity.moveTaskToBack(boolean nonRootActivity).
If you are handling activity's orientation changes with android:configChanges="orientation|screenSize" and onConfigurationChanged(Configuration newConfig), onRestoreInstanceState() will not be called.
It is not necessary that onRestoreInstanceState will always be called after onSaveInstanceState.
Note that :
onRestoreInstanceState will always be called, when activity is rotated (when orientation is not handled) or open your activity and then open other apps so that your activity instance is cleared from memory by OS.
In my case, onRestoreInstanceState was called when the activity was reconstructed after changing the device orientation. onCreate(Bundle) was called first, but the bundle didn't have the key/values I set with onSaveInstanceState(Bundle).
Right after, onRestoreInstanceState(Bundle) was called with a bundle that had the correct key/values.
I just ran into this and was noticing that the documentation had my answer:
"This function will never be called with a null state."
https://developer.android.com/reference/android/view/View.html#onRestoreInstanceState(android.os.Parcelable)
In my case, I was wondering why the onRestoreInstanceState wasn't being called on initial instantiation. This also means that if you don't store anything, it'll not be called when you go to reconstruct your view.
I can do like that (sorry it's c# not java but it's not a problem...) :
private int iValue = 1234567890;
function void MyTest()
{
Intent oIntent = new Intent (this, typeof(Camera2Activity));
Bundle oBundle = new Bundle();
oBundle.PutInt("MYVALUE", iValue); //=> 1234567890
oIntent.PutExtras (oBundle);
iRequestCode = 1111;
StartActivityForResult (oIntent, 1111);
}
AND IN YOUR ACTIVITY FOR RESULT
private int iValue = 0;
protected override void OnCreate(Bundle bundle)
{
Bundle oBundle = Intent.Extras;
if (oBundle != null)
{
iValue = oBundle.GetInt("MYVALUE", 0);
//=>1234567890
}
}
private void FinishActivity(bool bResult)
{
Intent oIntent = new Intent();
Bundle oBundle = new Bundle();
oBundle.PutInt("MYVALUE", iValue);//=>1234567890
oIntent.PutExtras(oBundle);
if (bResult)
{
SetResult (Result.Ok, oIntent);
}
else
SetResult(Result.Canceled, oIntent);
GC.Collect();
Finish();
}
FINALLY
protected override void OnActivityResult(int iRequestCode, Android.App.Result oResultCode, Intent oIntent)
{
base.OnActivityResult (iRequestCode, oResultCode, oIntent);
iValue = oIntent.Extras.GetInt("MYVALUE", -1); //=> 1234567890
}
Related
Here is my setup:
Activity -> FragmentWithPages -> ViewPager{Fragment1, Fragment2}
From Fragment1 I launch a DialogFragment and then from the DialogFragment I launch activityForResult for an implicit camera intent to take picture.
Problem:
Sometimes, when returning from the camera my app crashes inside the onActivityResult of Fragment1. Why does this happen? Now understand the chain of callback of onActivityResult. It would be coming back in the order of Activity.onActivityResult -> FragmentWithPages.onActivityResult -> Fragment1.onActivityResult -> DialogFragment.onActivityResult. So my question is, why is DialogFragment null when I do mDialogFragment.onActivityResult(…)?
I imagine it might have to do with memory: the system kills my app and then restarts it after the Camera app returns. But if that were the case, why is the DialogFragment the broken link in the chain? What can I do to prevent this problem or handle it as if nothing went wrong?
So no I do not mean to simply catch the NPE, that does not really do much as it would void the whole purpose for which I took the picture.
When you return from a startActivityForResult(), there's no guarantee that the activity instance will be the same. The system is free to dispose of the activity that started for result, and reconstruct it to handle the onActivityResult() call.
If this happens, it means of course that any instance fields you had previously assigned will no longer be so. It's up to you to use onSaveInstanceState() and reconstruct your activity instance using the saved instance state Bundle passed in to your onCreate().
You can test this scenario by setting "don't keep activities" in developer settings on your device.
This is a common situation where the system destroys your activity because it needs more resources. You need to implement the onSaveInstanceState method and then restore the instance state in the onCreate or onRestoreInstanceState method.
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putExtra("yourData", yourParcelableData);
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// keep the fragment and all its data across screen rotation
setRetainInstance(true);
if(savedInstanceState != null){
yourParcelableData = (yourParcelableData)savedInstanceState.getExtra("yourData");
}
}
There is a library that allows you to do it easier.
https://github.com/frankiesardo/icepick
I have FragmentA hosted by ActivityA. When the user selects an item from the options menu, ActivityB is started which hosts FragmentB. For now, I want to retain a String and a boolean from FragmentA by overriding onSaveInstanceState(), so when the user returns to FragmentA, their information is preserved.
Code from FragmentA:
#Override
public void onSaveInstanceState(Bundle savedInstanceState)
{
//LOGS SHOW THAT THIS IS ALWAYS CALLED WITH CORRECT VALUES
super.onSaveInstanceState(savedInstanceState);
savedInstanceState.putString("string", "example");
savedInstanceState.putBoolean("boolean", bool);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState)
{
View v = inflater.inflate(R.layout.fragmentA, parent, false);
//LOGS SHOW THAT THIS IS ALWAYS NULL
if (savedInstanceState != null)
{
if (savedInstanceState.getString("text") != null)
{
mObject.setText(savedInstanceState.getString("string"));
}
bool = savedInstanceState.getBoolean("boolean");
}
...
}
From reading previous problems similar to mine:
1) I decided to place the code to recover the information in onCreateView() because onCreate() will not always be called. (Although tests with the code in onCreate() also have the same problem.)
2) I also did not call setRetainInstance(true), since this will cause Bundle savedInstanceState to always be null.
3) I made sure that the XML layout for FragmentA has an id. The various children elements of this layout also have ids.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/fragmentA"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
...
</LinearLayout>
Despite this, Bundle savedInstanceState is always null. What am I missing here?
Any help appreciated. Thanks!
Use the onPause() to save your persistent data, that's referring to data that you would like to keep permanently, and hence, you save it in your SharedPreferences or your Database. onSaveInstanceState on the other hand retrains the data in case an activity is destroyed and you'd like to get that data back, a good scenario would be a user filling in a form. You said in your example that you're navigating from Activity A to Activity B, then you go back to Activity A, when you first navigate, Activity A is not destroyed, it's only sent to the background, so when you return to it, it's already there and will be brought to the foreground, your values should actually be there unchanged, and onCreate and onCreateView will not be called as the Activity is still alive (although it might be killed in case of low memory on device).
Best and fastest way to test your onSaveInstanceState is the most often used scenario, Orientation Change, orientation change will cause the activity to get destroyed completely and re-created, so allow orientation change on Activity A, put some values in your saveStateBundle and rotate the device, now this will call all your methods from the start, onCreate, onCreateView, ... etc to create the activity with the appropriate layout, your savedInstanceState should not be null now.
Note this is assuming your application is staying alive, if you're going to close the app completely and still want to keep the data, then put your information in the SharedPreferences or Database and retrieve them when you start the app again.
// Edit 1
to show you how to store values in SharedPreferences anywhere in your application, these values are persistent even if your application is closed. (although onSaveInstanceState should be enough to what you're looking for but hope this helps)
// SharedPreference
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
// Storing
preferences.edit().putString("valueNameOrKey", StringValue).commit();
// Retrieving
preferences.getString("valueNameOrKey", defaultValueToReturnInCaseThatKeyIsNotFound);
// Edit 2
To remove any key/value pair from the SharedPreferences you can do this:
preferences.edit().remove("valueNameOrKey").commit();
But then pay attention to what happens when you retrieve the value, since the key will not be available, it's going to return the default value instead like this:
preferences.getString("valueNameoOrKey", ""); // "" Is my default value since I'm using a String
since you can use putString, putInt, putBoolean etc, same goes for the get functions, your default value must match the expected return type.
The savedInstanceState is null when no data was been previously saved. To save data you must override the onSaveInstanceStateBundle(Bundle) method as described in the Android documentation:
you should use the onPause() method to write any persistent data (such as user edits) to storage. In addition, the method onSaveInstanceState(Bundle) is called before placing the activity in such a background state, allowing you to save away any dynamic instance state in your activity into the given Bundle, to be later received in onCreate(Bundle) if the activity needs to be re-created. See the Process Lifecycle section for more information on how the lifecycle of a process is tied to the activities it is hosting. Note that it is important to save persistent data in onPause() instead of onSaveInstanceState(Bundle) because the latter is not part of the lifecycle callbacks, so will not be called in every situation as described in its documentation.
More info here
I am storing some data in static varibles in Activity1 and accessing in Activity3,and Activity 5. ie..
Activity1---> Activity2--->Activ3
.....................|
......................Activity4.-----> Activ5
This works fine if we close the application completely, from Activity1 (ie if the user is at Activ5 if he clicks back button then -->Activ4-->Activ2-->Activ1-->Exit)
But the user is exiting app at Activ3,4,5 by clicking Mobile exit button(Not the application exit), Now after few hrs the user is reopening application then , It(app) gets started from Activi3 or 4 or 5. (ie where ever app was closed).
Now, Since i am using some data(which i stored in static varibles in Activ1.)
I am getting null values. Why this is happining. How to avoid this types of errors.
I have used sharedpref to avoid this.Is this the only solution ?
Restore the state of activity when it is recreated, so that the values passed can be retrieved at a later time.
e.g. for an integer that was passed through intent do as following: -
//this will save the value if an activity is killed in background.
#Override
protected void onSaveInstanceState(Bundle outState)
{
getIntent().putExtra("count", getIntent().getStringExtra("count"));
super.onSaveInstanceState(outState);
}
//In restore instance state, retrieve the stored values. The following work can also be done //in oncreate, as when an activity is killed in background, onCreate method is also called.
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState)
{
if(savedInstanceState == null)
return;
int count = getIntent().getIntExtra("count", 0);
super.onRestoreInstanceState(savedInstanceState);
}
You need to add onSaveInstanceState methods to your earlier activities, and check the bundle received by the onCreate methods. Check out the Activity Lifecycle for details.
You should not store values in static members, the activity context gets released, thus you losing your static values. The preferred way of passing values between activities is using Bundles along with the Intents.
you can create new class and extend application ,and in that store all data that you want, its very useful but remember if you do that you must add name of your application in manifest file
background
I have at least 2 activities on an app i've developing : a splash activity and a main activity. the splash activity calls the main activity.
on some cases (for now let's assume it's the first run only of the app) the splash activity adds a boolean extra (using intent.putExtra) to the intent to be true, and the main activity reads it using :
getIntent().getBooleanExtra(...,false);
the problem
i only wish to see the flag as true when i open the main activity after the splash activity.
This is why i've tried to just call (in the onCreate, right after i get the flag) :
getIntent().removeExtra(...);
another approach (acccording to this website) would be:
final Intent newIntent = new Intent();
setIntent(newIntent);
and another approach could be:
getIntent().putExtra(..., false);
none of those work: for some reason, on some cases, the flag is still returned as true.
as an example, i can press the home button (when the main activity was in the foreground), and then i start a heavy app (like the cut-the-rope game or a benchmark app), and then return to the app using the launcher.
in this case (which doesn't always occur), the splash activity isn't shown, but instead the main activity is shown. it calls onCreate again, while the flag itself is still set to true .
the question
why does it occur? how come the intent doesn't get reset?
is there a way to overcome this in an elegant way?
is it safe to just ignore the flag when the "savedInstanceState" is not null ?
It sounds like your VM is being torn down and re-started. I have observed on some devices that when the VM is re-created, instead of entering the launch activity, the last running activity is reconstructed from the saved state. It sounds like you are attempting to save state in the activity intent, so that is probably where your problems are coming from.
An important key to avoiding problems like this is to remember that the savedInstanceState bundle passed to onCreate is always null when the activity is being created new, in all other cases such as reconstructing after rotation or even when the VM has been torn down (as is likely your case) the savedInstance bundle passed to onCreate will be non-null.. my advice would be to use the intent to initialize your local state (the initial value of your flag) when the savedInstanceState is null, then never try to update your own intent, save your state in the savedInstance bundle..
E.G.
class Someactivity extends Activity {
private static final STATE_FLAG = "flagState";
private boolean someFlag;
protected void onCreate (Bundle savedInstanceState) {
final Intent intent = getIntent();
if(savedInstanceState==null) {
// Brand new, set from intent
someFlag = intent.getBooleanExtra(STATE_FLAG, false);
} else {
// Restored, set from saved instance
someFlag = savedInstanceState.getBoolean(STATE_FLAG, false);
}
}
protected void onSaveInstanceState (Bundle outState) {
// Save any updates to our activity state
outState.putBoolean(STATE_FLAG, someFlag);
}
}
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?
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.
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.
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);
}