Back navigation with Fragments / Toolbar

16,327

Solution 1

Ok so I managed to solve this after some trial and error. Two changes made:

  1. Implement addOnBackStackChangedListener
  2. ActionBarDrawerToggle's setToolbarNavigationClickListener needed to be set

As I only have one activity (everything else is Fragment classes) I added the backstack listener to the Parent Activity's onCreate method:

getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
        @Override
        public void onBackStackChanged() {
            if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
                getSupportActionBar().setDisplayHomeAsUpEnabled(true);
            } else {
                getSupportActionBar().setDisplayHomeAsUpEnabled(false);
            }
        }
    });

This resolved the disappearing back arrow when going back to the fragment. Finally added the listener to my NavigationDrawer's setup class:

mDrawerToggle.setToolbarNavigationClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                getActivity().onBackPressed();
            }
        });

I suppose the only questions I have is everything pointed towards using the onOptionsItemSelected method with android.R.id.home but this never worked for me. It might be the way I've implemented things of course but if someone sees anything obvious as to why please do let me know!

Solution 2

These steps helps you to show back button in toolbar when a fragment is loaded. And to pop out when its clicked.

Set setNavigationOnClickListener to toolbar in you activity.

final DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
    toggle = new ActionBarDrawerToggle(
            this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
    drawer.setDrawerListener(toggle);
    toggle.syncState();

    toolbar.setNavigationOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
          if(getSupportFragmentManager().getBackStackEntryCount() > 0){
              getSupportFragmentManager().popBackStack();
          }else {
              drawer.openDrawer(GravityCompat.START);
          }
        }
    });

Implement FragmentManager.OnBackStackChangedListener in you Activity. And register it with SupportFragmentManager in OnCreate()

 getSupportFragmentManager().addOnBackStackChangedListener(this);

OnBackStackChangedListener Implementation method:

@Override
public void onBackStackChanged() {
    if(getSupportFragmentManager().getBackStackEntryCount() > 0){
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
    }else {
        getSupportActionBar().setDisplayHomeAsUpEnabled(false);
        toggle.syncState();
    }
}

Solution 3

For me the above answer was not enough, but i've used @Hamz4h_ and added some more after digging into the ActionBarDrawerToggle class. I'm just calling this method of mine from the activity's onCreate:

private void initNavigationElements() {
    final ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
            this, mBinding.drawerLayout, mBinding.appBarMain.toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
    mBinding.drawerLayout.addDrawerListener(toggle);

    // Tricking the toggle by giving it its own arrow as a custom indicator. 
    // It will use it when setDrawerIndicatorEnabled is called with false
    toggle.setHomeAsUpIndicator(toggle.getDrawerArrowDrawable());
    toggle.syncState();

    getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
        @Override
        public void onBackStackChanged() {
            DrawerArrowDrawable drawerArrowDrawable = toggle.getDrawerArrowDrawable();
            if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
                // 1 - Display as arrow
                drawerArrowDrawable.setProgress(1);
                toggle.setDrawerIndicatorEnabled(false);

            } else {
                // 2 - Display as arrow menu
                drawerArrowDrawable.setProgress(0);
                toggle.setDrawerIndicatorEnabled(true);
            }
        }
    });

    toggle.setToolbarNavigationClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // This is called only when setDrawerIndicatorEnabled is set as false, meaning we are not at the "root" fragment.
            getSupportFragmentManager().popBackStackImmediate();
        }
    });
}

Hope this will help someone :)

Share:
16,327
Hamz4h_
Author by

Hamz4h_

Updated on June 20, 2022

Comments

  • Hamz4h_
    Hamz4h_ almost 2 years

    I'm scratching my head with this one now.... I have an ActionBarActivity that loads an initial Fragment - the original menu is inflated within the activity. Now, I have a navigation bar that, when an item is selected, loads a different fragment and adds this to the backstack.

    When I do this, there are a couple of things I want to set:

    1. Set the home as up indicator
    2. Invalidate the options menu from the main activity
    3. Set has options to true for the Fragment
    4. Ensure that the up indicator correctly navigates back to the original Fragment

    Something rather strange is going on - the up indicator appears once only and does not behave as the back button and although I've invalidated and inflated a new menu, the new menu gets appended to the original Activity menu.

    EDIT: Ok I've resolved the appending issue - forgot to add menu.clear() in the onCreateOptionsMenu method.

    My navigation drawer layout has onClick methods to all menu items which would trigger the load of another Fragment:

    public void navItemClick(View view) {
    
            switch (view.getId()) {
                case R.id.ripSMS:
                    mNavigationDrawer.toggleHome(false);
                    getSupportActionBar().setDisplayHomeAsUpEnabled(true);
                    FragmentTransaction mTrans = getSupportFragmentManager().beginTransaction();
                    mTrans.replace(R.id.voiceover_frame_layout,new MessageFragment(),"main_ui")
                            .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE).addToBackStack("msg").commit();
                    break;
                case R.id.ripEmail:
                    break;
                case R.id.ripSettings:
                    break;
            }
    
            mNavigationDrawer.closeDrawer();
        }
    

    toggleHome:

    public void toggleHome(boolean show) {
            mDrawerToggle.setDrawerIndicatorEnabled(show);
    
            if (!show) {
                mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
            } else {
                mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
            }
        }
    

    Once the item is triggered the onCreate contains the invalidate and the hasOptions code:

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getActivity().invalidateOptionsMenu();
        setHasOptionsMenu(true);
    }
    

    The onCreateOptionsMenu then inflates another menu layout (contains a single item called settings).

    As mentioned, this only partially works once - the first time I use the item to load the Fragment, I get the back icon but it's also not working (this is set within onOptionsItemSelected to check for the home item press - it does nothing). When I press the back button it takes me back to the correct place. If I navigate back however, the back arrow now longer shows even though the code runs through onCreate!

  • Wahib Ul Haq
    Wahib Ul Haq about 7 years
    This is the only solution which worked for me after a lot of struggle. Thanks for sharing! Calling toggle.syncState(); was a key factor because otherwise burger menu is not visible once you go back.