Up Button Calls OnDestroy of Parent Activity - android

Right off the top, I want to clarify something: The button that I am struggling with is NOT the back button. I am referring to the up/home button in the ActionBar / Toolbar at the top of the app, not the Android button at the bottom. There are a few posts of a similar nature, but they address the back button, not the up button.
Here is the situation: I have an Activity A that has a ListView fragment. When the user clicks on a list view item, it launches Activity B. Pretty typical. Activity A has an EditText field in the toolbar that allows the user to enter a search parameter. If the user hits the up/home button from Activity B, I return successfully to Activity A. However, I want Activity A to show the same text in the EditText field that was there when they left it. If the user hits the back button, this text is restored. But if they navigate with the up/home button, the EditText field is empty.
Using some log statements, I can see that when a list item is tapped from activity A, onSaveInstanceState and onStop are both called (but onDestroy is NOT called at that point.) From activity B, when the up/home button is tapped, onDestroy from activity A is immediately called, followed by onCreate, etc. However, the bundle savedInstanceState is null, presumably since onDestroy was just called.
Why is onDestroy called when returning to Activity A? This makes no sense to me. Here is what I have in the manifest:
<activity
android:name=".Activity.ActivityA"
android:label="#string/app_name"
android:parentActivityName=".Activity.ParentActivity"
android:theme="#style/AppTheme"
android:launchMode="singleTop"
android:windowSoftInputMode="stateVisible" />
<activity
android:name=".Activity.ActivityB"
android:label="#string/app_name"
android:parentActivityName=".Activity.ActivityA"
android:theme="#style/AppTheme" />
Here are the relevant methods in Activity A:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_search);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
actionBar = getSupportActionBar();
if (actionBar != null)
initializeActionBar();
if (getSupportActionBar() != null)
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
Log.d(TAG, "on create");
if (savedInstanceState != null) {
Log.d(TAG, "saved instance state not null");
if (savedInstanceState.getString("search_text") != null)
etSearch.setText(savedInstanceState.getString("search_text"));
}
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("search_text", etSearch.getText().toString());
Log.d(TAG, "on Save instance state");
}
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
Log.d(TAG, "on restore instance state");
if (savedInstanceState != null) {
if (savedInstanceState.getString("search_text") != null)
etSearch.setText(savedInstanceState.getString("search_text"));
}
}
#Override
protected void onResume() {
super.onResume();
Log.d(TAG, "on resume");
}
#Override
protected void onStop() {
super.onStop();
Log.d(TAG, "on stop");
}
#Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "on destroy");
}
private void initializeActionBar() {
actionBar.setCustomView(R.layout.actionbar_with_edittext);
etSearch = (EditText) actionBar.getCustomView().findViewById(R.id.actionbar_searchfield);
etSearch.setOnEditorActionListener(new TextView.OnEditorActionListener() {
#Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if(event != null && event.getKeyCode() == KeyEvent.KEYCODE_ENTER && event.getAction() == KeyEvent.ACTION_DOWN) {
initiateNewSearch();
etSearch.clearFocus();
}
return false;
}
});
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(etSearch, InputMethodManager.SHOW_IMPLICIT);
etSearch.requestFocus();
}
I don't think that any of the code in Activity B is relevant here.
This is my console output when a user taps on a listview item in Activity A:
on Save instance stateon stop
And then this is what is generated when the user taps on the up/home button from activity B:
on destroy on create on resume
If there is anything else that may be of help, please let me know. Thanks for any advice!

I don't know why default up button implementation creates a new activity but a working solution for me is to override onOptionsItemSelected:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if(id== android.R.id.home ){
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
Also this solution works for me:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
Intent intent = NavUtils.getParentActivityIntent(this);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
| Intent.FLAG_ACTIVITY_SINGLE_TOP);
NavUtils.navigateUpTo(this, intent);
return true;
default:
break;
}
return super.onOptionsItemSelected(item);
}

I had this problem too, and after a little more digging found the correct solution is to make a change in the manifest to add the following to your parent activity:
android:launchMode="singleTop"
Details are explained here.

Related

Why does my activity reset when I tap on the back button?

I have a back button in one activity and when I tap on it to return to the parent activity it will reset the parent activity. It's like onCreate() is being called again. I'm not sure why that is because when you tap on the back button it just calls finish() to exist the activity I'm currently in.
Here is how I'm declaring the toolbar:
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_item);
Toolbar toolbar = (Toolbar) findViewById(R.id.my_toolbar);
setSupportActionBar(toolbar);
if(getSupportActionBar() != null)
{
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
}
This is what happens when you tap on the button:
public boolean onOptionsItemSelected(MenuItem item)
{
if (item.getItemId()==android.R.id.home)
{
finish();
}
return super.onOptionsItemSelected(item);
}
The strange this is that when I hit the save button I return to the parent activity without any reset. So I'm not sure why this is happening.
You need to return true or else it will always call the onCreat() method. Also, you can create an "empty" intent and just not process it on the activity you return true.
#Override
public boolean onOptionsItemSelected(MenuItem item)
{
if (item.getItemId()==android.R.id.home)
{
Intent intent = new Intent();
setResult(Intent_Constant.TAPPED_BACK_BUTTON, intent);
finish();
return true;
}
return super.onOptionsItemSelected(item);
}

Going back to the previous activity and fragment from an additional back button

I have an app with tab navigation between fragments. One of these fragments has an option to open a new activity. When I use the built in device back button from this activity it goes back to the tabbed activity with the previous fragment tab selected.
I have added a back button to the action bar of an activity in my app by using:
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
and setting the parent activity in the manifest, but this button always navigates back to the first tab of the parent activity, rather than the one that was previously visible.
How can I make this back button behave in the same way as the device back button?
Do something like this:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch(id) {
case android.R.id.home:
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
onBackPressed() method:
#Override
public void onBackPressed() {
super.onBackPressed();
}
Handle back event in this manner
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch(id) {
case android.R.id.home:
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
in back press method
#Override
public void onBackPressed() {
Intent intent = new Intent(SecondActivity.this,TabbedActivity.class);
intent.putExtra("IsBack",true);
startActivity(intent);
}
in your tabbed activity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tabs);
if(getIntent().getExtras().getBoolean("IsBack")){
//navigate to your desire fragment
}
}

Maintain Activity state when changing to a different Activity

I have the following scenario:
Activity A > Activity B
Activity A < Activity B
What i would like to do is keep the state of Activity A when clicking Activity B's back button.
Activity A code:
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_product_details);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
if (toolbar != null)
{
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
// check for saved instance
if (savedInstanceState != null)
{
//restore saved values
}
else
{
//initialize members with default values
}
}
#Override
public void onSaveInstanceState(Bundle savedInstanceState)
{
savedInstanceState.putString("typeID", typeID);
super.onSaveInstanceState(savedInstanceState);
}
#Override
public void onRestoreInstanceState(Bundle savedInstanceState)
{
super.onRestoreInstanceState(savedInstanceState);
typeID = savedInstanceState.getString("typeID");
}
public boolean gotoActivityB(View view)
{
Intent intent = new Intent(getApplicationContext(), ActivityB.class);
startActivity(intent);
return false;
}
Activity B code:
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_buy_item);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
if (toolbar != null)
{
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
//do some magic...
}
public boolean onOptionsItemSelected(MenuItem item)
{
Intent intent = new Intent(getApplicationContext(), ActivityA.class);
startActivity(intent);
return false;
}
Every time i go from Activity B to Activity A, savedInstanceState is equal to null, in other words, Activity A state isn't saved or restored.
What am i missing here?
!!SOLUTION!!
Based on #cybersam's answer, Activities maintain their state by default. So there is no need for the savedInstanceState. To solve my problem i only had to update my back button events to:
public boolean onOptionsItemSelected(MenuItem item)
{
finish();
return true;
}
As the documentation for onSaveInstanceState() states:
An example when onPause() is called and not
onSaveInstanceState(Bundle) is when activity B is launched in front of
activity A: the system may avoid calling onSaveInstanceState(Bundle)
on activity A if it isn't killed during the lifetime of B since the
state of the user interface of A will stay intact.
So you cannot assume that onSaveInstanceState() would be called on A just because B is launched in front of it. In fact, most of the time, it will not be.
[EDITED]
Your code for B seems to be calling startActivity() to "go back" to the prior activity. If you just want B to go back to the prior activity, you can (usually) just call finish() to exit B, which should allow A to reappear (with its state intact), since it will become the top Activity in the stack.

Why is onBackPressed() not being called?

I am attempting to override onBackPressed(). However it appears to not detect when I click the back button in the action bar.
I currently have this code:
#Override
public void onBackPressed() {
Log.i("DATA", "Hit onBackPressed()");
super.onBackPressed();
}
The log message never appears in the LogCat. I know that this log statement works because it is copied from another method with a different message that DOES display in the LogCat.
I have searched for answers, and I have tried using onKeyDown and detecting if it is the BACK button being clicked but I still have the same issue.
Information about the project:
Android Studio 0.9.3
Method is located in blank activity
target sdk 21
minimum sdk 15
testing device is a Samsung Galaxy 5 (not emulator)
Any help would be greatly appreciated!!
EDIT:
This is a copy of my working code (this is test code so the activity name is not descriptive):
public class MainActivity2 extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_activity2);
getActionBar().setDisplayHomeAsUpEnabled(true);//Displays the back button
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main_activity2, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
Log.i("DATA", "Hit Actionbar Back Button");
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}
The message "Hit Actionbar Back Button" now appears in the LogCat.
onBackPressed() is invoked when user clicks on a hardware back button (or on the 'up' button in the navigation bar), not the button in the action bar. For this one you need to override onOptionsItemSelected() method. Example:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
// click on 'up' button in the action bar, handle it here
return true;
default:
return super.onOptionsItemSelected(item);
}
}
Please try this code,
public class MainActivity2 extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
#Override
public void onBackPressed() {
// TODO Auto-generated method stub
super.onBackPressed();
Toast.makeText(getApplicationContext(), "back press is call", Toast.LENGTH_LONG).show();
}
}

actionbar up navigation with fragments

I have a tabbed Actionbar/viewpager layout with three tabs say A, B, and C. In tab C tab(fragment),I am adding another fragment say fragment D. with
DFragment f= new DFragment();
ft.add(android.R.id.content, f, "");
ft.remove(CFragment.this);
ft.addToBackStack(null);
ft.commit();
I modify actionbar in DFragment's onResume to add up button:
ActionBar ab = getActivity().getActionBar();
ab.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
ab.setDisplayHomeAsUpEnabled(true);
ab.setDisplayShowHomeEnabled(true);
Now in DFragment, when I press hardware(phone) Back button, I return to the original Tabbed(ABC) layout with CFragment selected. How can I achieve this functionality with actionbar up button?
Implement OnBackStackChangedListener and add this code to your Fragment Activity.
#Override
public void onCreate(Bundle savedInstanceState) {
//Listen for changes in the back stack
getSupportFragmentManager().addOnBackStackChangedListener(this);
//Handle when activity is recreated like on orientation Change
shouldDisplayHomeUp();
}
#Override
public void onBackStackChanged() {
shouldDisplayHomeUp();
}
public void shouldDisplayHomeUp(){
//Enable Up button only if there are entries in the back stack
boolean canGoBack = getSupportFragmentManager().getBackStackEntryCount()>0;
getSupportActionBar().setDisplayHomeAsUpEnabled(canGoBack);
}
#Override
public boolean onSupportNavigateUp() {
//This method is called when the up button is pressed. Just the pop back stack.
getSupportFragmentManager().popBackStack();
return true;
}
I got it. just override onOptionsItemSelected in hosting activity and popup the backstack, e.g.
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home: {
FragmentManager fm = getSupportFragmentManager();
if (fm.getBackStackEntryCount() > 0) {
fm.popBackStack();
return true;
}
break;
}
}
return super.onOptionsItemSelected(item);
}
Call getActionBar().setDisplayHomeAsUpEnabled(boolean); and getActionBar().setHomeButtonEnabled(boolean); in onBackStackChanged() as explained in an answer below.
If you have one parent activity and want this up button to work as a back button, you can use this code:
add this to the onCreate in your main activity class
getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
int stackHeight = getSupportFragmentManager().getBackStackEntryCount();
if (stackHeight > 0) { // if we have something on the stack (doesn't include the current shown fragment)
getSupportActionBar().setHomeButtonEnabled(true);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
} else {
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
getSupportActionBar().setHomeButtonEnabled(false);
}
}
});
and then add onOptionsItemSelected like so:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
getSupportFragmentManager().popBackStack();
return true;
....
}
I generally use this all the time and seems pretty legit
you can go back with up button like back button ;
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
super.onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
I used a combination of Roger Garzon Nieto's and sohailaziz's answers. My app has a single MainActivity, and fragments A, B, C that are loaded into it. My "home" fragment (A) implements OnBackStackChangedListener, and checks the size of the backStack; if it's less than one, then it hides the UP button. Fragments B and C always load the back button (in my design, B is launched from A, and C is launched from B). The MainActivity itself just pops the backstack on UP button tap, and has methods to show/hide the button, which the fragments call:
MainActivity:
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
// Respond to the action bar's Up/Home button
case android.R.id.home:
getSupportFragmentManager().popBackStack();
return true;
}
return super.onOptionsItemSelected(item);
}
public void showUpButton() { getSupportActionBar().setDisplayHomeAsUpEnabled(true); }
public void hideUpButton() { getSupportActionBar().setDisplayHomeAsUpEnabled(false); }
fragmentA (implements FragmentManager.OnBackStackChangedListener):
public void onCreate(Bundle savedinstanceSate) {
// listen to backstack changes
getActivity().getSupportFragmentManager().addOnBackStackChangedListener(this);
// other fragment init stuff
...
}
public void onBackStackChanged() {
// enable Up button only if there are entries on the backstack
if(getActivity().getSupportFragmentManager().getBackStackEntryCount() < 1) {
((MainActivity)getActivity()).hideUpButton();
}
}
fragmentB, fragmentC:
public void onCreate(Bundle savedinstanceSate) {
// show the UP button
((MainActivity)getActivity()).showUpButton();
// other fragment init stuff
...
}
I know this question is old, but may be someone (like me) also needs it.
If your Activity extends AppCompatActivity, you can use a simpler (two-step) solution:
1 - Whenever you add a non-home fragment just show the up button, right after commiting the fragment transaction. Like this:
// ... add a fragment
// Commit the transaction
transaction.commit();
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
2 - Then when UP button is pressed, you hide it.
#Override
public boolean onSupportNavigateUp() {
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
return true;
}
That's it.
This worked for me. Override onSupportNavigateUp and onBackPressed, for example (code in Kotlin);
override fun onBackPressed() {
val count = supportFragmentManager.backStackEntryCount
if (count == 0) {
super.onBackPressed()
} else {
supportFragmentManager.popBackStack()
}
}
override fun onSupportNavigateUp(): Boolean {
super.onSupportNavigateUp()
onBackPressed()
return true
}
Now in the fragment, if you display the up arrow
activity.supportActionBar?.setDisplayHomeAsUpEnabled(true)
Clicking on it takes you back the previous activity.
Kotlin:
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
...
supportFragmentManager.addOnBackStackChangedListener { setupHomeAsUp() }
setupHomeAsUp()
}
private fun setupHomeAsUp() {
val shouldShow = 0 < supportFragmentManager.backStackEntryCount
supportActionBar?.setDisplayHomeAsUpEnabled(shouldShow)
}
override fun onSupportNavigateUp(): Boolean =
supportFragmentManager.popBackStack().run { true }
...
}
This is a very good and reliable solution: http://vinsol.com/blog/2014/10/01/handling-back-button-press-inside-fragments/
The guy has made an abstract fragment that handles the backPress behaviour and is switching between the active fragments using the strategy pattern.
For some of you there maybe a little drawback in the abstract class...
Shortly, the solution from the link goes like this:
// Abstract Fragment handling the back presses
public abstract class BackHandledFragment extends Fragment {
protected BackHandlerInterface backHandlerInterface;
public abstract String getTagText();
public abstract boolean onBackPressed();
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(!(getActivity() instanceof BackHandlerInterface)) {
throw new ClassCastException("Hosting activity must implement BackHandlerInterface");
} else {
backHandlerInterface = (BackHandlerInterface) getActivity();
}
}
#Override
public void onStart() {
super.onStart();
// Mark this fragment as the selected Fragment.
backHandlerInterface.setSelectedFragment(this);
}
public interface BackHandlerInterface {
public void setSelectedFragment(BackHandledFragment backHandledFragment);
}
}
And usage in the activity:
// BASIC ACTIVITY CODE THAT LETS ITS FRAGMENT UTILIZE onBackPress EVENTS
// IN AN ADAPTIVE AND ORGANIZED PATTERN USING BackHandledFragment
public class TheActivity extends FragmentActivity implements BackHandlerInterface {
private BackHandledFragment selectedFragment;
#Override
public void onBackPressed() {
if(selectedFragment == null || !selectedFragment.onBackPressed()) {
// Selected fragment did not consume the back press event.
super.onBackPressed();
}
}
#Override
public void setSelectedFragment(BackHandledFragment selectedFragment) {
this.selectedFragment = selectedFragment;
}
}
If you want to go back to your previous activity if this activity has an empty stack of fragments:
This could be useful if you have a MainActivity and you are navigating to e.g. a SettingsActivity with nested prefernceScreens. NavigateUp will pop fragments until you can finish the SettingsActivity to go back to parentActivity/root.
/**
* On actionbar up-button popping fragments from stack until it is empty.
* #return true if fragment popped or returned to parent activity successfully.
*/
#Override
public boolean onSupportNavigateUp() {
//Pop back stack if the up button is pressed.
boolean canGoBack = getSupportFragmentManager().getBackStackEntryCount()>0;
if (canGoBack) {
getSupportFragmentManager().popBackStack();
} else {
finish();
return super.onSupportNavigateUp();
}
return true;
}
Note: setDisplayHomeAsUpEnabled(true); in fragment activities onCreate()

Categories

Resources