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);
}
}
Related
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
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
}
Is there an built in way to save the contents of a listView as part of onSaveInstanceState to then restore later? I want the listView to look the same if the user hit the back button and now onCreate is being called again.
If you set your activity's launchMode to singleTask, then (unless the application was terminated / gc called upon) your data (list) will be preserved.
This way your device will hold only one running instance of your application at a time, so when you "launch it again" no matter from where, if it's already running in the background, then that instance will show up (with the latest data).
If there is a risk that your application was finished, and you still need the latest list of data to show up, this solution won't work.
But you could give a try to SharedPreferences: save the current data to the application's SharedPreferences, and restore it from there when launching it.
If it's ok, to have the predefined new list on each clean start of the application, but when getting it into foreground, you need the last seen items in your list, you should use the savedInstanceState parameter of your onCreate method:
private static final String MYLISTKEY = "myListLabels";
private ArrayList<String> listLabels = new ArrayList<String>();
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
if (savedInstanceState.containsKey(MYLISTKEY))
{
listLabels = savedInstanceState.getStringArrayList(MYLISTKEY);
}
else
{
// TODO: populate explicitely your list
}
}
#Override
protected void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
outState.putStringArrayList(MYLISTKEY, listLabels);
}
where listLabels contains the labels for your list.
It's not necessary for them to be of type String, you can put any type inside your Bundle.
When the user hits the back button the activity is always destroyed, so there will not be any restoring from savedinstance.
Android Training
When your activity is destroyed because the user presses Back or the activity finishes itself, the system's concept of that Activity instance is gone forever because the behavior indicates the activity is no longer needed.
I'm testing a destroy/restart sequence to make sure a counter retains its original value (before it was being incorrectly incremented on the restart). I put a fix in and it worked, when I test manually. But the unit test always passes, whether I include the fix or not. As you can see in the code below, I'm getting the counter value, then restarting, getting the counter value again and comparing them. What could be the problem?
public void testNumCorrectEqualAfterDestroy() {
mCorrect = (TextView) mActivity.findViewById(R.id.correct);
int before = Integer.parseInt(mCorrect.getText().toString());
mActivity.finish();
mActivity = this.getActivity();
mCorrect = (TextView) mActivity.findViewById(R.id.correct);
int after = Integer.parseInt(mCorrect.getText().toString());
Assert.assertEquals(before, after);
}
I think finish() will not cycle your activity through the "appropriate" states. The way I've tested this lifecycle case before is like so:
...
//TODO: do not use getActivity, instead use the startActivity() method
//and pass a value in the Bundle parameter
...
getInstrumentation().callActivityOnStart(mActivity);
getInstrumentation().callActivityOnResume(mActivity);
//TODO: asssert that the value is the expected one (based on what you fed in the bundle)
Bundle newBundle = new Bundle();
getInstrumentation().callActivityOnSaveInstanceState(mActivity, newBundle);
getInstrumentation().callActivityOnPause(mActivity);
getInstrumentation().callActivityOnStop(mActivity);
getInstrumentation().callActivityOnDestroy(mActivity);
//initialize activity with the saved bundle
getInstrumentation().callActivityOnCreate(mActivity, newBundle);
getInstrumentation().callActivityOnResume(mActivity);
//TODO: assert that the value is the expected one
ActivityInstrumentationTestCase2.getActivity() starts the Activity the first time you call it, and then it simply returns that Activity in each subsequent call in the test case. Thus, you are still looking at the Activity that you finished.
After you finish the first Activity, you need to start a new one from the test. You can use InstrumentationTestCase.launchActivity(), for example.
As another example, I've written a test that pushes a button in ActivityA that launches ActivityB for-result; the test then immediately kills ActivityA (via an orientation change, but finish() would work, too), and then the test gets a handle to the new ActivityA that the system creates when ActivityB is done and sends its result. The trick there was to have the test add an Instrumentation.ActivityMonitor and then have that monitor wait for the system to start the new ActivityA and give the test a handle to it.