I have an activity wich has a Toolbar which displays a back button.
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar_about"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:minHeight="?attr/actionBarSize"
android:theme="?attr/actionBarTheme"
app:title="#string/app_name"
/>
The back button is enabled like this:
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar_about);
setSupportActionBar(toolbar);
//noinspection ConstantConditions
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
I call this activity from my main activity like this:
Intent intent = new Intent(this, AboutActivity.class);
startActivity(intent);
The Activity's parent is defined in the manifest
<activity android:name=".AboutActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".EntryActivity" />
</activity>
So far everything works fine, except that the transition animation is wrong when using the back button in the Toolbar.
When I open the activity, it slides in from the right.
When I press the phone's physical back button it slides out to the right again. This is correct.
However when using the Toolbar back button it slides out to the left. This looks wrong. How can I change this, so it duplicates the behaviour of the physical back button?
When you press the Actionbar Up button, AppCompatActivity detects this button press in its onMenuItemSelected() call, and invokes onSupportNavigateUp(). This method determines the "parent activity" Intent and uses that to navigate up. Because it's using an Intent to (re-)open the previous activity, it uses the same animation it would for opening a "new" screen.
Assuming you don't care about the particular niceties of the "Up Navigation" pattern (which it sounds like you do not, as comments have led me to believe you don't have lateral navigation and you can't get to your second activity from anywhere other than your first activity), you can side-step all of this built-in "up" behavior by overriding the onSupportNavigateUp() method.
#Override
public boolean onSupportNavigateUp() {
finish();
return true;
}
This will mean that pressing the Actionbar Up button always simply finish()es your activity, so (like I said before) you lose out on all the smart built-in "up" behavior... but you didn't want that anyway.
You could also handle the Actionbar Up button in onOptionsItemSelected(), but I prefer the other way since I think it's a little more obvious that you're hijacking the system's up behavior.
#Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
With either of these in place, you can remove the "parent" definitions from your manifest, since now they're not used.
Try this:
override fun onSupportNavigateUp(): Boolean {
onBackPressed()
return true
}
This is because the default launchMode of activities is standard.
The documentation of standard states the following:
The system always creates a new instance of the activity in the target task and routes the intent to it.
You can solve this by using android:launchMode="singleTop" on the parent/starting activity.
If an instance of the activity already exists at the top of the target task, the system routes the intent to that instance through a call to its onNewIntent() method, rather than creating a new instance of the activity.
For more information see Tasks and the back stack and android:launchMode.
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 have the back arrow implimented and working but instead of just finishing the activityB and going back to the previous view (fragment) the user was on in activityA. It instead closes activityB and reloads activityA which means you have to go back through a few menus to get back to where you were.
The physical back arrow does exactly as I want it to in that it just closes the activtyB and puts the user back to where they were within activtyA originally before they moved to activtyB.
This is my current code for the toolbar back arrow but maybe I'm just not fully grasping how the physical back arrow works. I appreciate any and all advice!
toolbar = (Toolbar) findViewById(R.id.tool_bar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
For every activity you have to add parent activity in Android manifest file.
<activity
android:name="com.example.myfirstapp.ActivityC"
android:label="#string/title_activity_display_message"
android:parentActivityName="com.example.myfirstapp.MainActivity" >
<!-- The meta-data element is needed for versions lower than 4.1 -->
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.example.myfirstapp.MainActivity" />
</activity>
If you specified MainActivity as parent to ActivityC then it will go to MainActivity. If you specified ActivityB it will move to ActivityB on click on back button in ActionBar.
For more see at here
You need to override the onOptionsItemSelected method of your activity:
#Override
public boolean onOptionsItemSelected(MenuItem menuItem) {
if (menuItem.getItemId() == android.R.id.home) {
onBackPressed();
} else {
super.onOptionsItemSelected(menuItem);
}
return true;
}
I have an app with master/detail layout (1 activity, 1 ListView fragment and 1 detail fragment). When the user clicks an item in the ListView, a fragment transaction instantiates a detail fragment on the right-pane that includes the information corresponding to that item. When the detail fragment is shown I hide the initial action bar buttons/items and show 3 new AB items (done/delete/cancel). The user can clean the right-pane and return to the initial UI state by either pressing the back button or by pressing one of the 3 AB items.
The issue I'm experiencing is that when the user selects the app's home icon (i.e. "up navigation") the activity gets re-loaded (i.e. the animation that indicates that the activity is starting can be seen as both the action bar and the UI is been redrawn). The issue only happens when the app home icon is pressed. If the user presses the back button or a cancel/done/delete action bar button, the fragment is simply remove from the right-pane and the UI returns to initial state without any "re-loading".
The XML layout for the activity is the following (inside LinearLayout; prettify is hiding that line):
<fragment class="*.*.*.ListFragment"
android:id="#+id/titles" android:layout_weight="1"
android:layout_width="0px"
android:layout_height="match_parent" />
<FrameLayout android:id="#+id/details" android:layout_weight="2"
android:layout_width="0px"
android:layout_height="match_parent" />
The DetailsFragement has the actionBar.setDisplayHomeAsUpEnabled statement in its onCreate method:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
ActionBar actionBar = getSherlockActivity().getSupportActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
}
For both the ListView fragment and the Detail fragments the onCreateOptionsMenu() and onOptionsItemSelected() method are implemented within the fragments. Below the code for the Details fragment:
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.edit_menu, menu);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// some variable statements...
switch (item.getItemId()) {
case android.R.id.home:
//Toast.makeText(getSherlockActivity(), "Tapped home", Toast.LENGTH_SHORT).show();
onHomeSelectedListener.onHomeSelected();
return true;
case R.id.menu_edit_item_done:
editedTask.setTitle(editedTaskTitle);
onTaskEditedListener.onTaskEdited(editedTask, UPDATE_TASK, true);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
In the host activity I implement the onHomeSelectedListner to handle the app home icon press (i.e. "up navigation":
public void onHomeSelected(){
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
TaskFragment taskFragment = (TaskFragment)getSupportFragmentManager().findFragmentById(R.id.details);
ft.remove(taskFragment);
ft.commit();
manager.popBackStack();
}
The activity's listener in charged of handling all other action bar buttons (i.e. done/delete/cancel) is onTaskEditedListener and, aside of other code that processes some data, it has the same fragment transactions shown above.
Update(1/24)
Based on tyczj and straya feedback I placed log statements inside onCreate(), onResume(), onPause() of the activity to determine the differences between onHomeSelected and onTaskEdited listeners. I'm able to confirm that during the "up navigation" event (i.e. onHomeSelected) onPause(), onCreate() and onResume() are called. Whereas during the onTaskEdited call (i.e. back button or done/delete/cancel press) none of those events are called.
Update (1/25)
Based on a suggestion by Mark Murphy, I commented out the onHomeSelected method call in the "case android.R.id.home" statement just to see what would the Activity do. The thinking was that the app would do nothing since the are no statements. Turns out that is not the case. Even without a call to the listener method (i.e. that removes the fragment), the activity is restarted and the detail fragment is removed from the fragment container.
Update (2/28)
I temporarily workaround the fact that my main activity was getting restarted by disabling the window animations (as highlighted in my own answer). However, through further testing I uncovered a bug. Thanks to Wolfram Rittmeyer's sample code I was able to figure out the real reason(s) why my activity was restarting (in master/detail single layout) during up navigation:
1) Although I was using this "onHomeSelectedListener" to properly remove the fragment from the backstack, I still had some remnant code in the ListView fragment's onOptionsItemSelected that was creating a new intent to start the hosting activity. That's why pressing the app's home icon was re-starting the activity.
2) In my final implementation (shown in my own answer), I got rid of the onHomeSelectedListener in the activity and replace the startActivity intent (i.e. offending code) inside the ListView's onOptionsItemSelected to use the fragment removal + popBackStack code originally in the onHomeSelectedListener.
After much research and poking around, turns out that only reason why my activity was restarting during "up navigation" for master/detail configuration was because I left some code in the ListView Fragment's onOptionsItemSelected that was creating an intent to start the main activity in addition to my full fragment transaction code elsewhere. Below is the final implementation with which I got "up navigation" to work properly on both phone (multiple activities) and tablet (single activity/multi-pane) configurations. Thanks to Wolfram Rittmeyer for a couple of hints in his code (link in the comment section) that help me pinpoint my problem!
Main Activity: Hosts the fragments and performs some other app-specific operations
ListView Fragment: Handles "up navigation in table configuration
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
if(mDualPane){
FragmentManager manager = getSherlockActivity().getSupportFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
DetailFragment detailFragment = (DetailFragment)manager.findFragmentById(R.id.details);
ft.remove(detailFragment);
ft.commit();
manager.popBackStack();
getSherlockActivity().getSupportActionBar().setDisplayHomeAsUpEnabled(false);
getSherlockActivity().getSupportActionBar().setHomeButtonEnabled(false);
}
return true;
// Other case statements...
default:
return super.onOptionsItemSelected(item);
}
}
Details Fragment: Handles up navigation in phone configuration
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
// Sets "up navigation" for both phone/tablet configurations
ActionBar actionBar = getSherlockActivity().getSupportActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
if(!mDualPane){
Intent parentActivityIntent = new Intent(getSherlockActivity(), MainActivity.class);
parentActivityIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(parentActivityIntent);
getSherlockActivity().finish();
}
return true;
// Other case statements...
default:
return super.onOptionsItemSelected(item);
}
}
If you look at the Navigation Design Pattern you will see that you want to return to the starting activity when the home button is hit.
So say you have 2 Activities call them A1 and A2. Clicking on something in A1 takes you to A2. If the user hits the home button you should return them to A1 clearing the stack of everything up until that activity like this
Intent intent = new Intent(this, A1.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
this is what the flag Intent.FLAG_ACTIVITY_CLEAR_TOP does
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.
For example, consider a task consisting of the activities: A, B, C, D. If D calls startActivity() with an Intent that resolves to the component of activity B, then C and D will be finished and B receive the given Intent, resulting in the stack now being: A, B.`
The currently running instance of activity B in the above example will either receive the new intent you are starting here in its onNewIntent() method, or be itself finished and restarted with the new intent. If it has declared its launch mode to be "multiple" (the default) and you have not set FLAG_ACTIVITY_SINGLE_TOP in the same intent, then it will be finished and re-created; for all other launch modes or if FLAG_ACTIVITY_SINGLE_TOP is set then this Intent will be delivered to the current instance's onNewIntent().
This launch mode can also be used to good effect in conjunction with FLAG_ACTIVITY_NEW_TASK: if used to start the root activity of a task, it will bring any currently running instance of that task to the foreground, and then clear it to its root state. This is especially useful, for example, when launching an activity from the notification manager.
don't: break and then return super.onOptionsItemSelected(item), rather just: return true;
UPDATE:
So you're saying the Activity is "restarted" based on what you see happen with Views, but can you confirm what may or may not happen to the Activity (and Fragments for that matter) by using logging in the various lifecycle methods? That way you can be sure of what the current (erroneous) behaviour is before moving forward with diagnosis.
UPDATE:
OK, good to be sure about behaviour :)
Now regarding your question "What is the correct way to implement "up navigation" for a master/detail layout (1 activity/2fragments)? ":
The typical way is that the 2 Fragments got added within a single FragmentTransaction and you simply popBackStack to remove them and go back to whatever previous state was. I think you're doubling up by manually removing a Fragment within a FragmentTransaction and then popping backstack. Try just popBackStack. Oh and just to be sure and consistent, since you're using ActionBarSherlock and support.v4 are you using a FragmentActivity (rather than an Activity) and SherlockFragment?
I think you should handle the Up button only inside the activity.
If youre in a phone, the up button will be handled by activity that acts as a wrapper of that fragment, in tablet (master/detail pattern) you dont want it anyways
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.
I have a simple activity with a theme set via setTheme(), the theme id is stored in SharedPreferences, I get this data and setTheme() before super.OnCreate() in the main activity. On pressing the menu button I can launch a preferences activity. On updating preferences and pressing the back button to return to the main activity the theme does not update to the new setting. Only closing the app and reopening fixes this.
What's the best way to make the main activity update and reload the theme after the back button is pressed in the preferences activity? I tried putting setTheme() in OnResume but to no avail.
Any help is greatly appreciated,
Thanks Ric
See this page:
According to Dianne Hackborn, Android framework engineer:
You can only set the theme during
creation. To apply a theme, the
entire UI need to be reinflated and
rebuilt from its resources.
If there is no other way, you could do something like this in your activity, after updating the preferences:
Intent i = new Intent(this, YourActivity.class);
startActivity(i);
finish();
It's not too elegant, and I do hope there is a simpler way.
and setTheme() before super.OnCreate() in the main activity
You shouldn't ever put any code before super.onCreate() or super.onResume(), all your code should be placed after these calls. Maybe this is the problem.
register onsharedpreferencechange listener in your activity
Here is the way I did it:
I have an app that has multiple themes to choose from on a page. Normally, when a theme is selected and then the back button is pressed, the theme reverts back to the previous one. This is an issue.
I used the #Override trick for onBackPressed() to do the trick. Following is the code (I also modified the code for the first activity in the stack [my Navigation Drawer]).
#Override
public void onBackPressed()
{
Intent intent1 = new Intent(this, NavigationActivity.class);
startActivity(intent1);
super.onBackPressed(); // optional depending on your needs
}
That code in the Theme Switch activity made it so the normal back button code wasn't called to destroy the current activity and revert to the old theme. It just passes to the previous activity that the user was on, which is my NavigationActivity. Customize this how you will.
Another thing to note is that the back button can still plague you even after this, because once it passes me to NavigationActivity, if I press the back button again the theme is reverted. I called the same method as before, but I took out the super.onBackPressed(); statement which is what caused the reverting. This is what it looks like:
#Override
public void onBackPressed() {
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
}
}
I hope this does the trick for you!
If you use Navigation component please try as below mentioned. It is not required to call intent or finishActivity.
override fun onBackPressed() {
when (navController.currentDestination?.id){
R.id.settingsFragment -> super.onSupportNavigateUp()
else -> super.onBackPressed()
}
}
super.onSupportNavigateUp() will be solved your back navigation problem.