I am using a single activity and multiple fragments(screenshot attached) within the same activity to provide a seamless navigation. But after implementing the latest toolbar and navigation view, it seems hard to handle the navigation and home buttons. I am having trouble with the following things.
I have tried several things like overriding onBackPressed(), setHomeAsUpIndicator, popping fragments manually. Earlier i was using ActionBarDrawer toggle to handle this but it is failing somehow now. I checked the google samples they seem to use separate activities at most of the places.
Can anyone guide me how to implement a proper back navigation to handle the NavigationView, Back button in inner fragments and page titles? I am using AppCompatActivity, android.app.Fragment, NavigationView and Toolbar.
It's much easier to illustrate with some sort of division of responsibility for your Activity
and Fragment
.
Problem 1: Managing the Hamburger/Back button at left top. Toggling the icon and functionality to Menu and Back nav.
From the illustration, the solution should be encapsulated by the Activity
, which will look something like this:
public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {
private ActionBarDrawerToggle mDrawerToggle;
private DrawerLayout mDrawer;
private ActionBar mActionBar;
private boolean mToolBarNavigationListenerIsRegistered = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
mActionBar = getSupportActionBar();
mDrawer = (DrawerLayout) findViewById(R.id.drawer_layout);
mDrawerToggle = new ActionBarDrawerToggle(this, mDrawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
mDrawer.addDrawerListener(mDrawerToggle);
mDrawerToggle.syncState();
NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(this);
// On orientation change savedInstanceState will not be null.
// Use this to show hamburger or up icon based on fragment back stack.
if(savedInstanceState != null){
resolveUpButtonWithFragmentStack();
} else {
// You probably want to add your ListFragment here.
}
}
@Override
public void onBackPressed() {
if (mDrawer.isDrawerOpen(GravityCompat.START)) {
mDrawer.closeDrawer(GravityCompat.START);
} else {
int backStackCount = getSupportFragmentManager().getBackStackEntryCount();
if (backStackCount >= 1) {
getSupportFragmentManager().popBackStack();
// Change to hamburger icon if at bottom of stack
if(backStackCount == 1){
showUpButton(false);
}
} else {
super.onBackPressed();
}
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
} else if (id == android.R.id.home) {
// Home/Up logic handled by onBackPressed implementation
onBackPressed();
}
return super.onOptionsItemSelected(item);
}
@SuppressWarnings("StatementWithEmptyBody")
@Override
public boolean onNavigationItemSelected(MenuItem item) {
// Handle navigation view item clicks here.
int id = item.getItemId();
// Navigation drawer item selection logic goes here
mDrawer.closeDrawer(GravityCompat.START);
return true;
}
private void replaceFragment() {
/**
* Your fragment replacement logic goes here
* e.g.
* FragmentTransaction ft = getFragmentManager().beginTransaction();
* String tag = "MyFragment";
* ft.replace(R.id.content, MyFragment.newInstance(tag), tag).addToBackStack(null).commit();
*/
// The part that changes the hamburger icon to the up icon
showUpButton(true);
}
private void resolveUpButtonWithFragmentStack() {
showUpButton(getSupportFragmentManager().getBackStackEntryCount() > 0);
}
private void showUpButton(boolean show) {
// To keep states of ActionBar and ActionBarDrawerToggle synchronized,
// when you enable on one, you disable on the other.
// And as you may notice, the order for this operation is disable first, then enable - VERY VERY IMPORTANT.
if(show) {
// Remove hamburger
mDrawerToggle.setDrawerIndicatorEnabled(false);
// Show back button
mActionBar.setDisplayHomeAsUpEnabled(true);
// when DrawerToggle is disabled i.e. setDrawerIndicatorEnabled(false), navigation icon
// clicks are disabled i.e. the UP button will not work.
// We need to add a listener, as in below, so DrawerToggle will forward
// click events to this listener.
if(!mToolBarNavigationListenerIsRegistered) {
mDrawerToggle.setToolbarNavigationClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onBackPressed();
}
});
mToolBarNavigationListenerIsRegistered = true;
}
} else {
// Remove back button
mActionBar.setDisplayHomeAsUpEnabled(false);
// Show hamburger
mDrawerToggle.setDrawerIndicatorEnabled(true);
// Remove the/any drawer toggle listener
mDrawerToggle.setToolbarNavigationClickListener(null);
mToolBarNavigationListenerIsRegistered = false;
}
// So, one may think "Hmm why not simplify to:
// .....
// getSupportActionBar().setDisplayHomeAsUpEnabled(enable);
// mDrawer.setDrawerIndicatorEnabled(!enable);
// ......
// To re-iterate, the order in which you enable and disable views IS important #dontSimplify.
}
}
Problem 2: Page title - Changing the page titles whenever a fragment in pushed and popped.
Essentially, this can be handled in the onStart
for each Fragment
i.e. your ListFragment, DetailsFragment and CommentsFragment look something like this:
@Override
public void onStart() {
super.onStart();
// where mText is the title you want on your toolbar/actionBar
getActivity().setTitle(mText);
}
Probably worth having setRetainInstance(true)
in the onCreate
of your fragments as well.