This is my savedInstaceState code:
#Override
public void onSaveInstanceState(Bundle savedInstanceState)
{
savedInstanceState.putStringArrayList("todo_arraylist", Altodo);
Log.v("bundle", "Saved");
super.onSaveInstanceState(savedInstanceState);
}
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
if (savedInstanceState != null)
{
Altodo = savedInstanceState.getStringArrayList("todo_arraylist");
Log.v("bundle", "Restored");
}
else
{
Log.v("bundle", "null");
}
setContentView(R.layout.main);
}
The logs always show the "bundle save" tag.
But in onCreate method, SavedInstanceState is always null.
I observed the exact same symptoms (reported as issue 133394) in a project with two Activities A and B that extend ActionBarActivity. Activity A is the main activity, and I always receive null for savedInstanceState in onCreate of its list fragment when returning from a detail view activity B. After many hours, this problem exposed itself to me as a navigation issue in disguise.
The following may be relevant to my setup and come from other answers on this page:
Given this answer, I made sure that fragment and activity each have unique IDs set.
There is no override of onSaveInstanceState without super call.
Activity A is specified as acitivy B's parent in AndroidManifest.xml, using both the android:parentActivityName attribute and the corresponding meta-data tag for earlier versions of Android (see "Providing Up Navigation").
Already without any corresponding creation code such as getActionBar() .setHomeButtonEnabled(true), activity B has a functioning back button (<) in its action bar. When this button is tapped, activity A reappears but with (a) all previous instance state lost, (b) onCreate always called, and (c) savedInstanceState always null.
Interestingly, when I tap the back button provided at the bottom edge of the emulator display (an open triangle that points to the left), activity A reappears just as it was left (i.e. its instance state fully retained) without invoking onCreate. So maybe something is wrong with navigation?
After more reading, I implemented my own navigation instructions to run in response to a tap on the back-button in activity B:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home)
NavUtils.navigateUpFromSameTask(this);
return true;
}
return super.onOptionsItemSelected(item);
}
Nothing related to restoring instance state of activity A changed. NavUtils also provide a method getParentActivityIntent(Activity) and navigateUpTo(Activity, Intent) that allow us to modify the navigation intent to explicitly instruct that activity A is not started fresh (and thus without saved instance state provided) by setting the FLAG_ACTIVITY_CLEAR_TOP flag:
If set, and the activity being launched is already running in the
current task, then instead of launching a new instance of that
activity, all of the other activities on top of it will be closed and
this Intent will be delivered to the (now on top) old activity as a
new Intent.
In my hands, this solves problem of lost instance state and could look like:
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId()== android.R.id.home) {
Intent intent = NavUtils.getParentActivityIntent(this);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
NavUtils.navigateUpTo(this, intent);
return true;
}
return super.onOptionsItemSelected(item);
}
Note that this may not be the complete solution in other cases where a user can switch directly to activity B from within a different task (see here). Also, a possibly identical solution in behavior that does not make use of NavUtils is to simply call finish():
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId()== android.R.id.home) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
Both solutions work in my hands. I am only speculating that the original issue is a slightly incorrect default implementation of the back-button, and it may be related to that implementation invoking some kind of navigateUp that misses FLAG_ACTIVITY_CLEAR_TOP.
Did you check if you have an Id set for that view ( if a view it is/has...). onSaveInstanceState() is not called otherwise.
Check this link.
The state saved in this manner is not persisted. If the whole application is killed as you are doing during debugging, the bundle will always be null in onCreate.
This IMO is yet another example of awful Android documentation. It's also why most apps in the marketplace don't implement saving state properly (at all).
in Manifest add this line for activities
android:launchMode="singleTop"
for example:
<activity
android:name=".ActivityUniversity"
android:label="#string/university"
android:launchMode="singleTop"
android:parentActivityName="com.alkhorazmiy.dtm.ActivityChart">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.alkhorazmiy.dtm.ActivityChart" />
</activity>
How do you test it?
Imo the best way to test it is using the "Don't keep activities"-flag in Settings > Developer Options. If you don't have Developer Options in Settings, see Enabling On-device Developer Options.
Open your activity
Long-press home
Go to another application
Long-press home
Go back to your application
Shouldn't super.onSaveInstanceState(savedInstanceState); be the first line in your override?
Edit: War_Hero points out in the comments that the documentation on that topic indicates that no, it shouldn't be the first line.
Check your activity in AndroidManifest.xml and remove android:noHistory property if is true.
<activity
// ....
android:noHistory="false" />
To debug, consider implementing onRestoreInstanceState and placing a call to Log.d in this method. Then, in the emulator, hit ctrl-F11 or whatever to rotate the phone. Your call to Log.d should be hit.
Implement a method of onRestoreInstanceState
and put below code there
Altodo = savedInstanceState.getStringArrayList("todo_arraylist");
I found that when I override onSaveInstanceState() and actually save some data in the Bundle, instance state is restored. Otherwise it's not.
Ive managed same way arround. Instead of handling savedInstanceState Bundle on the onCreateView method, ive handled it on onCreate method and setting the passed value to a globar variable then acessing this variable on the onCreateView method.
Hope it helps.
https://developer.android.com/guide/topics/manifest/activity-element#lmode
From this you can see 'Similarly, if you navigate up to an activity on the current stack, the behavior is determined by the parent activity's launch mode.' Maybe you are in the 'standard' mode.
I was able to solve it with:
#Override public boolean onSupportNavigateUp()
{
onBackPressed();
return true;
}
still had parent set in the manifest. So when you press the up navigation button, now it acts like the back button.
Related
In my activity, I have:
<activity
android:name=".ChildActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.example.ParentActivity" />
</activity>
ChildActivity can be accessed via a standard launcher icon on the device.
My problem is when the user accesses ChildActivity via the launcher icon, then presses the Up button in the action bar, the app exits rather than going up to ParentActivity. This is because ParentActivity hasn't been instantiated.
Not sure if it's the best way, but I am trying to solve the problem by overriding onSupportNavigateUp(). However, I don't know how I can detect if the parent activity has been instantiated:
#Override
public boolean onSupportNavigateUp() {
boolean hasParentActivityBeenInstantiated = ???;
if (hasParentActivityBeenInstantiated) {
return super.onSupportNavigateUp();
}
else {
Intent upIntent = NavUtils.getParentActivityIntent(this);
startActivity(upIntent);
finish();
return true; // Up navigation completed successfully and this Activity was finished.
}
}
So how can I determine if the parent activity has been instantiated? More importantly, in this case, is overriding onSupportNavigateUp() the right way to navigate to the parent activity?
I would suggest not to guess if parent activity is instantiated or not, but use android:launchMode="singleTop" for parent activity. I think it is also fine to use onSupportNavigateUp unless you refactor your app to be single-activity with single navigation graph. Imo navigation framework leaves no choices for multi activity setup.
If for some fancy reason you need to explicitly know if it's up, you can use various techniques including static field initialization, flagging some field inside the application instances etc.
I did similar implementation (kotlin) and I was using onBackPressed for the purpose but it probably doesn't suite your situation (I was handling all the descendant fragments sys back press) however I would suggest using TaskStackBuilder if your depth of child/parent is bigger then one:
override fun onBackPressed() {
val upIntent: Intent? = NavUtils.getParentActivityIntent(this)
checkNotNull(upIntent) { "No parent activity intent" }
TaskStackBuilder.create(this)
.addNextIntentWithParentStack(upIntent)
.startActivities()
finish()
}
I would like to be able to detect if my Activity has been obscured by, say, a system alert or some other overlay (for example the power menu when I long press on the power button), or some malware that detects the launch of my Activity. I noticed that the foreground app in this case would still be my app, so I can't simply base it on what the foreground app is. I also notice that onPause() isn't called when my Activity is obscured, so I can't put any logic in onPause() either. Even if I can though, I would then have to differentiate between a system alert/overlay and the user pressing the back/home button.
Are there any other ways for me to accomplish this?
You can check if Activity, Fragment or View is Obscured.
For Activity you need override dispatchTouchEvent method and check if event has flag FLAG_WINDOW_IS_OBSCURED. There is example code:
public class OverlayTouchActivity extends Activity {
private boolean mObscuredTouch;
public boolean isObscuredTouch() {
return mObscuredTouch;
}
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
mObscuredTouch = (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0;
return super.dispatchTouchEvent(event);
}
}
This is a part of Android code, please check OverlayTouchActivity.java. In order to check if Fragment is obscured, execute the following piece of code in Fragment that belongs to the OverlayTouchActivity activity:
OverlayTouchActivity activity = (OverlayTouchActivity) getActivity();
if (activity.isObscuredTouch()) {
// Fragment is bbscured
}
Please see AppPermissionsFragment.java fragment (search for OverlayTouchActivity).
For View you should override onFilterTouchEventForSecurity method. For more information please see security section of View documentation.
You can use the PackageManager to query whose of the installed packages has suspect permissions like SYSTEM_ALERT_WINDOW, BIND_ACCESSIBILITY_SERVICE or BIND_DEVICE_ADMIN.
I'm new to Android development, and what I playing around with is a sports type app with a app flow like: League -> Team -> Player -> Player stats (this is in a ViewPager). All using Fragments.
I have the flow working in this direction, but I'm trying to use home button to navigate back up the stack (back button works too). My problem is that going forward through the flow I pass the ID of the League, then Team, then Player, etc... But when the home button is pressed, this data is no longer available.
I've tried setting retainInstance to true, but that doesn't do it. Not sure why, but the ID fields are all null in onCreate whenever I press back or the home button.
I've also tried overriding onSaveInstance and onActivityCreated, and putting the ID's for each entity in the bundle there, but even though I save the ID in the bundle in onSaveInstance, the bundle is null in onActivityCreated.
How can I keep the ID's of each entity around for when the user hits the back or home buttons?
Thanks for any help.
I had a similar problem when I add the Settings Activity.
I had to modify the onMenuItemSelected created automatically.
#Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
if (!super.onMenuItemSelected(featureId, item)) {
// I removed this line
//NavUtils.navigateUpFromSameTask(this);
// and add this 3 lines
Intent intent = NavUtils.getParentActivityIntent(this);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
NavUtils.navigateUpTo(this, intent);
}
return true;
}
return super.onMenuItemSelected(featureId, item);
}
navigateUpFromSameTask() not only set the FLAG_ACTIVITY_CLEAR_TOP flag. It also clears the savedInstanceState.
I solved this issue by storing the values that goes normaly in saveInstanceState in SharedPreferences. Storing them in onPause() and restoring in onResume().
[Update Solution]
Referring to the post in the link
ViewPager PagerAdapter not updating the View
public void onSomeButtonClicked(View view) { // registered to Button's android:onClick in the layout xml file
Log.w(TAG,"Some button clicked !!");
getIntent().setAction(IntentManager.Intents.ACTION_SPAWN_LIST_BY_SOMETHING);
mViewPager.getAdapter().notifyDataSetChanged();
}
// And inside my PagerAdapter
#Override
public int getItemPosition(Object object) {
return 0;
}
Fixed all my problems, i just used Intent.setAction().
[Update to below Post]
My problem is i have a ViewPager PagerAdapter in my Main Activity. On clicking one of the 3 buttons., any specific intent will be fired (i used intent, i could have gone with just State variable as well, just that i pass some Uri with the intent). What happens is., i do
public void onSomeButtonClicked(View view) { // registered to Button's android:onClick in the layout xml file
Log.w(TAG,"Some button clicked !!");
getIntent().setAction(IntentManager.Intents.ACTION_SPAWN_LIST_BY_SOMETHING);
mViewPager.getAdapter().notifyDataSetChanged();
}
This is why i was guessing maybe i should just do startActivity again, with the new Intent Action on the same Activity.
Spawning a new Activity, i would have to redraw every other view in the layout which is basically the same code, for the most part in the current activity.
Any suggestions here? Please help
[Original Post]
I have a PagerAdapter and 3 Buttons in the my Main Activity. This activity is enter from Main Launcher.
When i press any one of the buttons, the Intent Action is changed.
My question:
The changed Intent action reflects some changed view in the ViewPager and does_not spawn a new Activity as such, only the view is updated.
What approach should i take to get this task?
Can i start the currentActivity using startActivity() and different Intent actions on button click?
or is there any other efficient way in android to do this?
[No need code, just explanation of logic / method would suffice]
Thanks in advance
If you are saying that you are trying to use startActivity to bring up the same activity again, and its not working, it could be because you set something like singleTop in your Android manifest.
If you are asking whether or not you should use an intent to change the state of your Activity, then the answer is "it depends". If the user would expect the back button to return your app to its previous state (instead of going back to the home screen), then it might be a good choice for you. If that is the case, however, I would ask why not just make 2 different Activities? Otherwise, just do as Dan S suggested and update the state of your Activity as the user interacts with it.
You can always use the onNewIntent() hook. Do something like this in your activity:
protected void onNewIntent(Intent intent){
//change your activity based on the new intent
}
Make sure to set the activity to singleTop in your manifest. Now whenever startActivity is called the onNewIntent() will be executed. Also, note that per the documentation:
Note that getIntent() still returns the original Intent. You can use setIntent(Intent) to update it to this new Intent.
I want to know if user would return to the home screen if he exit the current activity.
I'm going to improve on the comment of #H9kDroid as the best answer here for people that have a similar question. (Original link)
You can use isTaskRoot() to know whether the activity is the root of a task.
UPDATE (Jul 2015):
Since getRunningTasks() get deprecated, from API 21 it's better to follow raukodraug answer or Ed Burnette one (I would prefer second one).
There's possibility to check current tasks and their stack using ActivityManager.
So, to determine if an activity is the last one:
request android.permission.GET_TASKS permissions in the manifest.
Use the following code:
ActivityManager mngr = (ActivityManager) getSystemService( ACTIVITY_SERVICE );
List<ActivityManager.RunningTaskInfo> taskList = mngr.getRunningTasks(10);
if(taskList.get(0).numActivities == 1 &&
taskList.get(0).topActivity.getClassName().equals(this.getClass().getName())) {
Log.i(TAG, "This is last activity in the stack");
}
Please note, that above code will be valid only if You have single task. If there's possibility that number of tasks will exist for Your application - You'll need to check other taskList elements. Read more about tasks Tasks and Back Stack
Hope this will help new beginners, Based above answers which works for me fine, i am also sharing code snippet so it will be easy to implement.
solution : i used isTaskRoot() which return true if current activity is only activity in your stack and other than i also handle case in which if i have some activity in stack go to last activity in stack instead of opening new custom one.
In your activity
#Override
public void onBackPressed() {
if(isTaskRoot()){
startActivity(new Intent(currentActivityName.this,ActivityNameYouWantToOpen.class));
// using finish() is optional, use it if you do not want to keep currentActivity in stack
finish();
}else{
super.onBackPressed();
}
}
there is an easiest solution to this, you can use isTaskRoot()
in your activity
One way to keep track of this is to include a marker when you start a new activity and check if the marker exists.
Whenever you start a new activity, insert the marker:
newIntent=new Intent(this, NextOne.class);
newIntent.putExtra(this.getPackageName()+"myself", 0);
startActivity(newIntent);
And you can then check for it like this:
boolean islast=!getIntent().hasExtra(this.getPackageName()+"myself")
While there may be a way to achieve this (see other answers) I would suggest that you shouldn't do that. Normal Android applications shouldn't need to know if the Home screen is about to display or not.
If you're trying to save data, put the data saving code in your onPause() method. If you're trying to give the user a way to change their mind about existing the application, you could intercept the key up/down for the Back key and the onBackPressed() method and present them with an "Are you sure?" prompt.
I've created a base class for all my activities, extending the AppCompatActivity, and which has a static counter:
public abstract class BasicActivity extends AppCompatActivity {
private static int activityCounter = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
++activityCounter;
...
}
#Override
public void onDestroy() {
super.onDestroy();
--activityCounter;
if(activityCounter==0) {
// Last instance code...
}
}
public boolean isLastInstance() { return (activityCounter==1); }
}
This has worked well enough, so far; and regardless of API version. It requires of course that all activities extends this base class - which they do, in my case.
Edit: I've noticed one instance when the counter goes down to zero before the app completely exits, which is when the orientation is changed and only one activity is open. When the orientation changes, the activity is closed and another is created, so onDestroyed is called for the last activity, and then onCreate is called when the same activity is created with the changed orientation. This behaviour must be accounted for; OrientationEventListener could possibly be used.
The Problem with sandrstar's solution using ActivityManager is: you need a permission to get the tasks this way.
I found a better way:
getIntent().hasCategory(Intent.CATEGORY_LAUNCHER)
The Activity on the Stack bottom should allways get this category by default while other Activities should not get it.
But even if this fails on some devices you can set it while starting your Activity:
Intent intent = new Intent(startingActivity, SomeActivityClass.class);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
activity.startActivity(intent);
Android implements an Activity stack, I suggest you read about it here. It looks like all you want to do though is retrieve the calling activity: getCallingActivity(). If the current activity is the first activity in your application and the application was launched from the home screen it should (I assume) return null.
The one thing that missed here, is the "Home key" click, when activated, you can't detect this from your activity, so it would better to control activity stack programmatically with handling "Back key" press and moving to required activity or just doing necessary steps.
In addition, you can't be sure, that starting your activity from "Recent Activity" list can be detected with presetting some extra data into intent for opening activity, as it being reused in that case.