Handling ActionBar title with the fragment back stack?

AndroidAndroid FragmentsAndroid ActionbarActionbarsherlock

Android Problem Overview


I have an Activity where I load in a ListFragment and, upon clicking, it drills down a level and a new type of ListFragment is shown, replacing the original one (using the showFragment method below). This is placed on the back stack.

At the beginning, the activity shows the default title in the action bar (i.e. it's set automatically based on the application's android:label).

When showing the list for the next level in the hierarchy, the name of the item clicked on should become the action bar's title.

However, when pressing Back, I would like the original default title to be restored. This isn't something FragmentTransaction knows about, so the title isn't restored.

I've vaguely read about FragmentBreadCrumbs, but this seems to require using a custom view. I'm using ActionBarSherlock and would prefer to not have my own custom title view.

What is the best way of doing this? Is it possible without a load of boilerplate code and having to keep track of the titles shown along the way?


protected void showFragment(Fragment f) {
  FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
  ft.replace(R.id.fragment_container, f);
  ft.addToBackStack(null);
  ft.commit();
}

Android Solutions


Solution 1 - Android

In every fragment and every activity I change the title like this. This way the active title will always be correct:

@Override
public void onResume() {
	super.onResume();
	// Set title
    getActivity().getActionBar()
    	.setTitle(R.string.thetitle);
}

There is some cases where onResume isn't called inside fragments. In some of these cases we can use:

public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    if(isVisibleToUser) {
        // Set title
        getActivity().getActionBar()
        	.setTitle(R.string.thetitle);
    }
}

Solution 2 - Android

As the original answer is quite old, this might come of help as well. As the documentation states, one might want to register a listener to listen on the back stack changes in the hosting Activity:

getSupportFragmentManager().addOnBackStackChangedListener(
        new FragmentManager.OnBackStackChangedListener() {
            public void onBackStackChanged() {
                // Update your UI here.
            }
        });

Then, identify the situation in the callback method and set a proper title, without accessing the ActionBar from the Fragment.

This is a more elegant solution as the Fragment doesn't have to know about the ActionBar existence and Activity is usually the place that is managing the backstack so having it handled over there seems to be more appropriate. Fragment should at all time be considered only by its own content, not the surroundings.

More on the topic in the [documentation][1].

[1]: http://developer.android.com/training/implementing-navigation/temporal.html#back-fragments "here"

Solution 3 - Android

Let the controlling activity do all the work as follows:

Listen for backstack events (in onCreate() of activity):

// Change the title back when the fragment is changed
    getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
        @Override
        public void onBackStackChanged() {
            Fragment fragment = getFragment();
            setTitleFromFragment(fragment);
        }
    });

Get the current fragment from the container:

/**
 * Returns the currently displayed fragment.
 * @return
 *      Fragment or null.
 */
private Fragment getFragment() {
    Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.container);
    return fragment;
}

Set the fragment inside the content view:

private void setFragment(Fragment fragment, boolean addToBackStack) {
    // Set the activity title
    setTitleFromFragment(fragment);
    .
    .
    .
}

Solution 4 - Android

Warpzit is right. This also solves title problem when orientation of device is changed. Also if you use support v7 for action bar, you can get action bar from fragment like this :

@Override
public void onResume() {
    super.onResume();
    ((ActionBarActivity)getActivity()).getSupportActionBar().setTitle("Home");
}

Solution 5 - Android

It is best to let the OS do as much of the work as possible. Assuming each fragment is properly named using .addToBackStack("title") then you can override onBackPressed something like this to achieve desired behavior:

// this example uses the AppCompat support library
// and works for dynamic fragment titles
@Override
public void onBackPressed() {
    FragmentManager fragmentManager = getSupportFragmentManager();
    int count = fragmentManager.getBackStackEntryCount();
    if (count <= 1) {
        finish();
    }
    else {
        String title = fragmentManager.getBackStackEntryAt(count-2).getName();
        if (count == 2) {
            // here I am using a NavigationDrawer and open it when transitioning to the initial fragment
            // a second back-press will result in finish() being called above.
            mDrawerLayout.openDrawer(mNavigationDrawerFragment.getView());
        }
        super.onBackPressed();
        Log.v(TAG, "onBackPressed - title="+title);
        getSupportActionBar().setTitle(title);
    }
}

Solution 6 - Android

I use a similar solution to Lee approach, but replacing onBackStackChanged() method instead.

First I set the fragment name when adding the transaction to the back stack.

getSupportFragmentManager().beginTransaction()
                .replace(R.id.frame_content, fragment)
                .addToBackStack(fragmentTitle)
                .commit();

Then I override the onBackStackChanged() method and I call setTitle() with the last backstack entry name.

@Override
public void onBackStackChanged() {
    int lastBackStackEntryCount = getSupportFragmentManager().getBackStackEntryCount() - 1;
    FragmentManager.BackStackEntry lastBackStackEntry =
            getSupportFragmentManager().getBackStackEntryAt(lastBackStackEntryCount);

    setTitle(lastBackStackEntry.getName());
}

Solution 7 - Android

Use Fragments method:

@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)

It is called on every Fragment appearance, but onResume is not.

Solution 8 - Android

The best approach is to make use of the android provided Interface OnBackStackChangedListener method onBackStackChanged().

Lets say we have a navigation drawer with 4 options to which the user can navigate to. In that case we will have 4 fragments. Lets see the code first and then I will explain the working.

    private int mPreviousBackStackCount = 0;
    private String[] title_name = {"Frag1","Frag2","Frag3","Frag4"};
    Stack<String> mFragPositionTitleDisplayed;

    public class MainActivity extends ActionBarActivity implements FragmentManager.OnBackStackChangedListener
    @Override
    protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	....
	....
	....
	getSupportFragmentManager().addOnBackStackChangedListener(this);
    mFragPositionTitleDisplayed = new Stack<>();
}

public void displayFragment() {
    Fragment fragment = null;
    String title = getResources().getString(R.string.app_name);
    switch (position) {
        case 0:
            fragment = new Fragment1();
            title = title_name[position];
            break;
        case 1:
            fragment = new Fragment2();
            title = title_name[position];
            break;
        case 2:
            fragment = new Fragment3();
            title = title_name[position];
            break;
        case 3:
            fragment = new Fragment4();
            title = title_name[position];
            break;
        default:
            break;
    }
    if (fragment != null) {
        FragmentManager fragmentManager = getSupportFragmentManager();
        fragmentManager.beginTransaction()
                .replace(R.id.container_body, fragment)
                .addToBackStack(null)
                .commit();
        getSupportActionBar().setTitle(title);
    }
}

@Override
public void onBackStackChanged() {
    int backStackEntryCount = getSupportFragmentManager().getBackStackEntryCount();
    if(mPreviousBackStackCount >= backStackEntryCount) {
        mFragPositionTitleDisplayed.pop();
        if (backStackEntryCount == 0)
            getSupportActionBar().setTitle(R.string.app_name);
        else if (backStackEntryCount > 0) {
            getSupportActionBar().setTitle(mFragPositionTitleDisplayed.peek());
        }
        mPreviousBackStackCount--;
    }
    else{
        mFragPositionTitleDisplayed.push(title_name[position]);
        mPreviousBackStackCount++;
    }

}   

In the code shown we have the displayFragment() method. Here I display the fragment on the basis of option chosen from the navigation drawer.The variable position corresponds to the position of the item clicked from the ListView or RecyclerView in the navigation drawer. I set the actionbar title accordingly with getSupportActionBar.setTitle(title), where the title stores the appropriate title name.

Whenever we click the item from nav drawer a fragment is displayed depending on the item clicked to the user. But on the back end side this fragment is added to the backstack and the method onBackStachChanged(), gets hit. What I have done is that I have created a variable mPreviousBackStackCount and initialized it to 0. I have also created an additional stack which will store the action bar title names. Whenever I add a new fragment to the backstack, I add the corresponding title name to my created stack. On the opposite side whenever I press the back button onBackStackChanged() is called and I pop the last title name from my stack and set the title to the name derived by the peek() method of the stack.

Example:

Lets say our android backstack is empty:

Press Choice 1 from nav drawer: onBackStachChanged() is called and the Fragment 1 is added to android backstack, backStackEntryCount is set to 1 and Frag1 is pushed to my stack and size of mFragPositionTitleDisplayed becomes 1.

Press Choice 2 from nav drawer: onBackStachChanged() is called and the Fragment 2 is added to android backstack, backStackEntryCount is set to 2 and Frag2 is pushed to my stack and size of mFragPositionTitleDisplayed becomes 2.

Now we have 2 elements both in the android stack and my stack. When you press back button onBackStackChanged() is called and the value of backStackEntryCount is 1. The code enters the if part and pops out the last entry from my stack. So, the android backstack has only 1 fragment - "Fragment 1" and my stack has only 1 title - "Frag1". Now I just peek() the title from my stack and set the action bar to that title.

Remember: To set the action bat title use peek() and not pop() else your application will crash when you open more than 2 fragments and try to go back by pressing back button.

Solution 9 - Android

You can Solve with onKeyDown! I have a bool mainisopen=true <-- MainFragment is Visible other Fragment mainisopen=false

and here is My Code:

public boolean onKeyDown(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_BACK && mainisopen == false) {
        mainisopen = true;
        HomeFrag fragment = new HomeFrag();
        FragmentTransaction fragmentTransaction =
                getSupportFragmentManager().beginTransaction();
        fragmentTransaction.replace(R.id.fragmet_cont, fragment);
        fragmentTransaction.commit();
        navigationView = (NavigationView) findViewById(R.id.nav_view);
        navigationView.getMenu().findItem(R.id.nav_home).setChecked(true);
        navigationView.setNavigationItemSelectedListener(this);
        this.setTitle("Digi - Home"); //Here set the Title back
        return true;
    } else {
        if (keyCode == KeyEvent.KEYCODE_BACK && mainisopen == true) {
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setMessage("Wollen sie die App schliessen!");
            builder.setCancelable(true);

            builder.setPositiveButton("Ja!", new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    System.exit(1);
                }
            });

            builder.setNegativeButton("Nein!", new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    Toast.makeText(getApplicationContext(), "Applikation wird fortgesetzt", Toast.LENGTH_SHORT).show();
                }
            });

            AlertDialog dialog = builder.create();
            dialog.show();

            return true;
        }
        return super.onKeyDown(keyCode, event);
    }

}

Solution 10 - Android

As described here my solution is adding this code to MainActivity onCreate method(): and changing actionbar title

FragmentManager fragmentManager=getSupportFragmentManager();
fragmentManager.addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
    @Override
    public void onBackStackChanged() {
        Fragment currentFragment = fragmentManager.findFragmentById(R.id.My_Container_1_ID);
        currentFragment.onResume();
    }
});

and changing actionbar title in fragment's onResume() method

@Override
public void onResume() {
    super.onResume();
    AppCompatActivity activity = (AppCompatActivity) getActivity();
    ActionBar actionBar = activity.getSupportActionBar();
    if(actionBar!=null) {
        actionBar.setTitle("Fragment Title");
        actionBar.setSubtitle("Subtitle");
    }

}

Solution 11 - Android

To update the actionbar title on back press. Just simply put

getActivity.setTitle("title")

inside onCreateView method.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionChristopher OrrView Question on Stackoverflow
Solution 1 - AndroidWarpzitView Answer on Stackoverflow
Solution 2 - AndroidMaciej PigulskiView Answer on Stackoverflow
Solution 3 - AndroidMeanmanView Answer on Stackoverflow
Solution 4 - AndroidJemshit IskenderovView Answer on Stackoverflow
Solution 5 - AndroidLee HounshellView Answer on Stackoverflow
Solution 6 - AndroidGnzltView Answer on Stackoverflow
Solution 7 - AndroidJurijs TurjanskisView Answer on Stackoverflow
Solution 8 - AndroidAbhishekView Answer on Stackoverflow
Solution 9 - AndroidBitDEVil2K16View Answer on Stackoverflow
Solution 10 - AndroidmDonmezView Answer on Stackoverflow
Solution 11 - AndroidAshish KumawatView Answer on Stackoverflow