android – How to handle bottom navigation perfectly with back pressed-ThrowExceptions

Exception or error:

I am working on a bottom navigation bar, but I am not getting perfectly bottom navigation bar.

My MainActivity class:

public class MainActivity extends AppCompatActivity {

    private static final String SELECTED_ITEM = "selected_item";

    private BottomNavigationView bottomNavigationView;
    private Toolbar toolbar;
    private MenuItem menuItemSelected;
    private int mMenuItemSelected;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        bottomNavigationView = (BottomNavigationView) findViewById(R.id.bottom_navigation);
        bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
            @Override
            public boolean onNavigationItemSelected(@NonNull MenuItem item) {
                selectFragment(item);
                return true;
            }
        });

        //Always load first fragment as default
        FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
        fragmentTransaction.replace(R.id.frameLayout, new AnnouncementFragment());
        fragmentTransaction.commit();

        if (savedInstanceState != null) {
            mMenuItemSelected = savedInstanceState.getInt(SELECTED_ITEM, 0);
            menuItemSelected = bottomNavigationView.getMenu().findItem(mMenuItemSelected);
        } else {
            menuItemSelected = bottomNavigationView.getMenu().getItem(0);
        }

        selectFragment(menuItemSelected);
    }

    private void selectFragment(MenuItem item) {
        Fragment fragment = null;
        Class fragmentClass;
        switch (item.getItemId()) {
            case R.id.action_announcement:
                fragmentClass = AnnouncementFragment.class;
                break;
            case R.id.action_menu:
                fragmentClass = MenuFragment.class;
                break;
            case R.id.action_menu_reports:
                fragmentClass = ReportFragment.class;
                break;
            case R.id.action_setting:
                fragmentClass = SettingFragment.class;
                break;

            default:
                fragmentClass = AnnouncementFragment.class;
        }

        try {
            fragment = (Fragment) fragmentClass.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        FragmentManager fragmentManager = getSupportFragmentManager();
        fragmentManager.beginTransaction().replace(R.id.frameLayout, fragment).commit();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        outState.putInt(SELECTED_ITEM, mMenuItemSelected);
        super.onSaveInstanceState(outState);
    }

And my back pressed also not working properly:

 @Override
    public void onBackPressed() {
        MenuItem homeItem = bottomNavigationView.getMenu().getItem(0);
        if (mMenuItemSelected != homeItem.getItemId()) {
            selectFragment(homeItem);
        } else {
            super.onBackPressed();
        }
    }

How should I do that because bottom menu has uneven distribution on bar. How to properly maintain the menu space without uneven distribution.

Here I am attaching my result which I obtain on AVD
First I

Another Selection

How to solve:

According to the guidelines for Material Design

On Android, the Back button does not navigate between bottom
navigation bar views.

EDIT: Material Design link no longer mentions back button behavior.

Pressing the back button you can quit the application, which is the default behavior, such as in Google Photo…

FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.content, fragment);
// note: there is NOT a addToBackStack call
fragmentTransaction.commit();

…or lead the user to the home section and then, if pushed again, at the exit.

Personally I find this last pattern much better.

To get it without override onBackPressed you need to identify the home fragment and differentiate it from all the others

navigation = (BottomNavigationView) findViewById(R.id.navigation);
navigation.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
    @Override
    public boolean onNavigationItemSelected(@NonNull MenuItem item) {
        switch (item.getItemId()) {
            case R.id.navigation_home:
                viewFragment(new HomeFragment(), FRAGMENT_HOME);
                return true;
            case R.id.navigation_page_1:
                viewFragment(new OneFragment(), FRAGMENT_OTHER);
                return true;
            case R.id.navigation_page_2:
                viewFragment(new TwoFragment(), FRAGMENT_OTHER);
                return true;
        }
        return false;
    }
});

What you have to do now is write the viewfragment method that have to:

  1. Know how many fragments there are in the stack before the commit
  2. If the fragment is not “home type”, save it to the stack before
    the commit

  3. Add an OnBackStackChangedListener that when the stack decreases,
    (i.e. when I pressed back ), delete all the fragments that are
    not “home type” (POP_BACK_STACK_INCLUSIVE) , bringing us to the home fragment

Below the full method with comments

private void viewFragment(Fragment fragment, String name){
    final FragmentManager fragmentManager = getFragmentManager();
    FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.replace(R.id.content, fragment);
    // 1. Know how many fragments there are in the stack
    final int count = fragmentManager.getBackStackEntryCount();
    // 2. If the fragment is **not** "home type", save it to the stack
    if( name.equals( FRAGMENT_OTHER) ) {
        fragmentTransaction.addToBackStack(name);
    }
    // Commit !
    fragmentTransaction.commit();
    // 3. After the commit, if the fragment is not an "home type" the back stack is changed, triggering the
    // OnBackStackChanged callback
    fragmentManager.addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
        @Override
        public void onBackStackChanged() {
            // If the stack decreases it means I clicked the back button
            if( fragmentManager.getBackStackEntryCount() <= count){
                // pop all the fragment and remove the listener
                fragmentManager.popBackStack(FRAGMENT_OTHER, POP_BACK_STACK_INCLUSIVE);
                fragmentManager.removeOnBackStackChangedListener(this);
                // set the home button selected
                navigation.getMenu().getItem(0).setChecked(true);
            }
        }
    });
}

###

Try this

@Override
    public void onBackPressed() {
        BottomNavigationView bottomNavigationView = (BottomNavigationView) findViewById(R.id.navigation);
        int seletedItemId = bottomNavigationView.getSelectedItemId();
        if (R.id.home != seletedItemId) {
            setHomeItem(MainActivity.this);
        } else {
            super.onBackPressed();
        }
    }

public static void setHomeItem(Activity activity) {
    BottomNavigationView bottomNavigationView = (BottomNavigationView)
            activity.findViewById(R.id.navigation);
    bottomNavigationView.setSelectedItemId(R.id.home);
}

###

@Override
    public void onBackPressed() {
        BottomNavigationView mBottomNavigationView = findViewById(R.id.navigation);
        if (mBottomNavigationView.getSelectedItemId() == R.id.navigation_home)
        {
            super.onBackPressed();
            finish();
        }
        else
        {
            mBottomNavigationView.setSelectedItemId(R.id.navigation_home);
        }
    }

###

This is maybe a little late but I think the best way to do it is as simple as this.

 @Override
public void onBackPressed() {
    if (mBottomNavigationView.getSelectedItemId() == R.id.action_home) {
        super.onBackPressed();
    } else {
        mBottomNavigationView.setSelectedItemId(R.id.action_home);
    }
}

I hope it helps and happy coding 🙂

###

onBackPressed did not worked for me. So this I used.

 @Override
 protected void onResume() {
    super.onResume();
    bottomNavigationView.getMenu().getItem(0).setChecked(true);
 }

###

The best way is: when in home button the app is closed and when in another button back in home button.

Below I put my code :

first i’m load home button in navigation View :

private void loadFragment(Fragment fragment) {
    Toast.makeText(this, "load", Toast.LENGTH_SHORT).show();
    FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
    transaction.replace(R.id.frame_container, fragment, TAG_FRAGMENT);
    transaction.commit();
}

and remember dont call addToBackStack() .
and then handle the situation by onBackPressed() :

@Override
public void onBackPressed() {
    if (navigation.getSelectedItemId() == R.id.bottomAkhbar) {
        super.onBackPressed();
    } else {
        navigation.setSelectedItemId(R.id.bottomAkhbar);
    }
}

###

Use addToBackStack Method when calling a fragment like this,

   getSupportFragmentManager().beginTransaction().addToBackStack(null).add(R.id.content_home_nav,newFragment).commit();

Use this code in your onBackPressed Method

if (getSupportFragmentManager().getBackStackEntryCount() > 0 ){
        getFragmentManager().popBackStack();
    } else {
        super.onBackPressed();
    }

###

For your requirement you would be working with fragments on navigation for this you can use Tablayout with view pager and make bottom navigation.

<android.support.design.widget.TabLayout
        android:id="@+id/tab_layout"
        android:layout_width="match_parent"
        android:layout_height="60dp"></android.support.design.widget.TabLayout>

and then setup viewpager with tab layout and add icon to tablayout in your activity

tabLayout = (TabLayout) findViewById(R.id.tab_layout);
    viewPager = (ViewPager) findViewById(R.id.controller_pager);
    viewPager.setAdapter(new ViewPagerAdapter(getSupportFragmentManager()));
    viewPager.setOffscreenPageLimit(4);
    tabLayout.setupWithViewPager(viewPager);
    tabLayout.getTabAt(0).setIcon(R.drawable.selector_home);
    tabLayout.getTabAt(1).setIcon(R.drawable.selector_contact);
    tabLayout.getTabAt(2).setIcon(R.drawable.selector_profile);
    tabLayout.getTabAt(3).setIcon(R.drawable.selector_settings);

now handle all things on the click of tablayout and it will work fine
tabLayout.addOnTabSelectedListener(this);

###

I was facing the same problem but after doing this I got the solution

First Paste this code in your main activity (where you are using Bottom navigation bar)

BottomNavigationView bottomNavigationView = (BottomNavigationView) findViewById(R.id.navigation);
BottomNavigationViewHelper.disableShiftMode(bottomNavigationView);

And then create a class named BottomNavigationViewHelper and paste the following code.

public class BottomNavigationViewHelper {
public static void disableShiftMode(BottomNavigationView view) {
    BottomNavigationMenuView menuView = (BottomNavigationMenuView) view.getChildAt(0);
    try {
        Field shiftingMode = menuView.getClass().getDeclaredField("mShiftingMode");
        shiftingMode.setAccessible(true);
        shiftingMode.setBoolean(menuView, false);
        shiftingMode.setAccessible(false);
        for (int i = 0; i < menuView.getChildCount(); i++) {
            BottomNavigationItemView item = (BottomNavigationItemView) menuView.getChildAt(i);
            //noinspection RestrictedApi
            item.setShiftingMode(false);
            // set once again checked value, so view will be updated
            //noinspection RestrictedApi
            item.setChecked(item.getItemData().isChecked());
        }
    } catch (NoSuchFieldException e) {
        Log.e("BNVHelper", "Unable to get shift mode field", e);
    } catch (IllegalAccessException e) {
        Log.e("BNVHelper", "Unable to change value of shift mode", e);
    }
}

}

Hope It helps

###

Try this.

getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
        @Override
        public void onBackStackChanged() {

            // Gets the previous global stack count
            int previousStackCount = mPreviousStackCount;

            // Gets a FragmentManager instance
            FragmentManager localFragmentManager = getSupportFragmentManager();

            // Sets the current back stack count
            int currentStackCount = localFragmentManager.getBackStackEntryCount();

            // Re-sets the global stack count to be the current count
            mPreviousStackCount = currentStackCount;

            boolean popping = currentStackCount < previousStackCount;

            if(popping){
                bottomNavigationView.getMenu().getItem(0).setChecked(true);
            }

        }
    });

###

Please try this solution.
I have made changes in your code given in question.

I have assumed that on back pressing first time your app will come back to home fragment (in your case Announcement fragment) and if you back press again, the app will close.

This flow will also reflect in bottom navigation bar.

public class MainActivity extends AppCompatActivity {
private static final String BACK_STACK_ROOT_TAG = "root_home_fragment";
private static final String SELECTED_ITEM = "selected_item";
private Fragment fragment;
private FragmentManager fragmentManager;
private BottomNavigationView bottomNavigationView;
private Toolbar toolbar;
private MenuItem menuItemSelected;
private int mMenuItemSelected;
private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
        = new BottomNavigationView.OnNavigationItemSelectedListener() {

    @Override
    public boolean onNavigationItemSelected(@NonNull MenuItem item) {
        boolean selected = false;
        switch (item.getItemId()) {
            case R.id.action_announcement:
                fragment = AnnouncementFragment.newInstance();
                selected =  true;
                break;
            case R.id.action_menu:
                fragment = MenuFragment.newInstance();
                selected =  true;
                break;
            case R.id.action_menu_reports:
                fragment = ReportFragment.newInstance();
                selected =  true;
                break;
        case R.id.action_setting:
        fragment = SettingFragment.newInstance();
                selected =  true;

        }
        if(fragment !=null){
            fragmentManager = getFragmentManager();
            switch (item.getItemId()) {
              case R.id.action_announcement:
                   // Pop every fragment from backstack including home fragment.
                   fragmentManager.popBackStack(BACK_STACK_ROOT_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE);
                   fragmentManager.beginTransaction()
                            .replace(R.id.content, fragment)
                            .addToBackStack(BACK_STACK_ROOT_TAG)
                            .commit();
                    break;
               default: 

                   fragmentManager.beginTransaction()
                            .replace(R.id.content, fragment)
                            .addToBackStack(null)
                            .commit();  
                   break;
           }
        }
        return selected;
    }

};


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);

    bottomNavigationView = (BottomNavigationView) findViewById(R.id.bottom_navigation);
    bottomNavigationView.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);

    //Always load first fragment as default
    bottomNavigationView.setSelectedItemId(R.id.action_announcement);

    if (savedInstanceState != null) {
        mMenuItemSelected = savedInstanceState.getInt(SELECTED_ITEM, 0);
        menuItemSelected = bottomNavigationView.getMenu().findItem(mMenuItemSelected);
    } else {
        menuItemSelected = bottomNavigationView.getMenu().getItem(0);
    }
}



@Override
protected void onSaveInstanceState(Bundle outState) {
    outState.putInt(SELECTED_ITEM, mMenuItemSelected);
    super.onSaveInstanceState(outState);
}

public void onBackPressed() {
    int count = getFragmentManager().getBackStackEntryCount();
    if(count >1){
        // We have lots of fragment on backstack to be popped.
        // Pop till the root fragment.
        getFragmentManager().popBackStack(BACK_STACK_ROOT_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE);
     bottomNavigationView.setSelectedItemId(R.id.action_announcement);
    }
    else{
        // Close the application when we are on home fragment.
        supportFinishAfterTransition();
    }
}
}

###

Most time when you, press back button, old fragment in the back stack,
are recalled. therefore the system call this onCreateView() method

there add this code

val bottomNav = activity?.findViewById<BottomNavigationView>(R.id.activity_main_bottom_navigation)
    bottomNav?.selectedItemId = R.id.the_id_of_the_icon__that_represent_the_fragment

###

I did this after trying all and everything and at last it worked -_- .I have pasted this 2 override method in my each and every activity that i am surfing through my bottom navigation.

  @Override
protected void onResume() {
    super.onResume();
    bottomNavigationView = (BottomNavigationView) findViewById(R.id.bottom_navigation_Menu_name);
    bottomNavigationView.getMenu().getItem(Menu_item_position).setChecked(true);

}

@Override
protected void onRestart() {
    super.onRestart();
    bottomNavigationView = (BottomNavigationView) findViewById(R.id.bottom_navigation_Menu_name);
    bottomNavigationView.getMenu().getItem(Menu_item_position).setChecked(true);
}

###

I had the same problem so I solved my problem using this method:
In my main activity, I have a bottom nav bar and a nav drawer, I need to sync items in my drawer and bottom nav:

I created a method for my main fragment and the others:
my main fragment replacer:

public void MainFragmentChanger(final Fragment fragment, final String TAG){
    if (main_page_fragment != null){
        fragmentTransaction = myFragmentManager.beginTransaction();
        fragmentTransaction.remove(main_page_fragment).commit();
    }
    if (main_drawer.isDrawerOpen()){
        main_drawer.closeDrawer();
    }
    new Handler().post(new Runnable() {
        @Override
        public void run() {
            main_page_fragment = fragment;
            main_page_fragment.setRetainInstance(true);
            fragmentTransaction = myFragmentManager.beginTransaction();
            fragmentTransaction.replace(R.id.main_container, main_page_fragment,TAG);
            fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
            fragmentTransaction.commitAllowingStateLoss();
        }
    });
}

and this is for my other fragment replacer:

public void changeBottomFragment(final Fragment fragment, final String TAG){
    if (main_drawer.isDrawerOpen()){
        main_drawer.closeDrawer();
    }
    new Handler().post(new Runnable() {
        @Override
        public void run() {
            main_page_fragment = fragment;
            main_page_fragment.setRetainInstance(true);
            fragmentTransaction = myFragmentManager.beginTransaction();
            fragmentTransaction.replace(R.id.main_container, main_page_fragment);
            fragmentTransaction.addToBackStack(null);
            fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
            fragmentTransaction.commitAllowingStateLoss();
        }
    });
}

so after that, I need to sync items in both drawer and bar:
Note I use material navigation drawer by Mike Penz, and com.ashokvarma.bottomnavigation.BottomNavigationBar for nav bar.
here is the method for that purpose:

public void changeNavAndBarStats(String tag){
    if (tag == "flash"){
        bottomNavigationBar.selectTab(2,false);
        main_drawer.setSelection(flashcards.getIdentifier(),false);
    }else if (tag == "dic"){
        bottomNavigationBar.selectTab(3,false);
        main_drawer.setSelection(dictionary.getIdentifier(),false);
    }else if (tag == "Home"){
        bottomNavigationBar.selectTab(0,false);
        main_drawer.setSelection(home.getIdentifier(),false);
    }
}

So I call my fragments Like this:

MainFragmentChanger(new MainPageFragment(),"Home");
                            bottomNavigationBar.selectTab(0,false);

changeBottomFragment(new FlashCardFragment(),"flash");
                            bottomNavigationBar.selectTab(2,false);

changeBottomFragment(new TranslateFragment(),"dic");
                            bottomNavigationBar.selectTab(3,false);

At the end I call changeNavAndBarStatus in my fragment’s onResume method:

((MainPageActivity)getContext()).changeNavAndBarStats("flash");

That’s it! you are good to go!

###

Late answer but here it goes.

Let’s say you have a BottomNavigation inside MainActivity with 4 Fragments.

  1. FragmentA
  2. FragmentB
  3. FragmentC
  4. FragmentD

If your are adding each fragment to backstack like so:

With kotlin:

 main_bottom_navigation.setOnNavigationItemSelectedListener { item ->
        var fragment: Fragment? = null
        when (item.itemId) {
            R.id.nav_a -> fragment = FragmentA()
            R.id.nav_b -> fragment = FragmentB()
            R.id.nav_c -> fragment = FragmentC()
            R.id.nav_d -> fragment = FragmentD()
        }

        supportFragmentManager
            .beginTransaction()
            .setCustomAnimations(R.anim.abc_fade_in, R.anim.abc_fade_out)
            .replace(R.id.home_content, fragment!!)
            .addToBackStack(fragment.tag)
            .commit()

        true
    }

You dont really need to override onBackPressed() inside the MainActivity but explicitly cast on each fragment and assigning the BottomNavigation like this:

FragmentA.kt

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    (activity as MainActivity).main_bottom_navigation.menu.getItem(0).isChecked = true
}

FragmentB.kt

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    (activity as MainActivity).main_bottom_navigation.menu.getItem(1).isChecked = true
}

FragmentC.kt

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    (activity as MainActivity).main_bottom_navigation.menu.getItem(2).isChecked = true
}

FragmentD.kt

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    (activity as MainActivity).main_bottom_navigation.menu.getItem(3).isChecked = true
}

Like this the fragment backstack will properly pop and even exit application when it reaches 0.

###

This is what I did to handle back press in the activity:

    @Override
public void onBackPressed() {
    super.onBackPressed();

    if(homeFragment.isVisible()){
        navView.setSelectedItemId(R.id.nav_home);
    }
    if(searchFragment.isVisible()){
        navView.setSelectedItemId(R.id.nav_search);
    }
    if(myProfileFragment.isVisible()){
        navView.setSelectedItemId(R.id.nav_profile);
    }

}

###

Try this to achieve the following:
on back press:
from home fragment exit the app.
from other fragments goto home fragment.

    //On Back Press if we are at a Fragment other than the Home Fragment it will navigate back to the
// Home Fragment. From Home Fragment it will exit the App.
@Override
public void onBackPressed() {
    int backStackEntryCount = getSupportFragmentManager().getBackStackEntryCount();
    if (backStackEntryCount == 0) {
        super.onBackPressed();  
    } else {
        goHome();
    }
}

    public void goHome() {
    //Following code will set the icon of the bottom navigation to active
    final BottomNavigationView mBottomNav = findViewById(R.id.nav_view);
    MenuItem homeItem = mBottomNav.getMenu().getItem(0);
    mBottomNav.setSelectedItemId(homeItem.getItemId());
    getSupportFragmentManager().popBackStackImmediate();

    //To delete all entries from back stack immediately one by one.
    int backStackEntry = getSupportFragmentManager().getBackStackEntryCount();
    for (int i = 0; i < backStackEntry; i++) {
        getSupportFragmentManager().popBackStackImmediate();
    }
    //To navigate to the Home Fragment
    final HomeFragment homeFragment = new HomeFragment();
    FragmentTransaction myFragmentTransaction = getSupportFragmentManager().beginTransaction();
    myFragmentTransaction.replace(R.id.nav_host_fragment, homeFragment, "HomeFrag Tag");
    myFragmentTransaction.commit();
}

Leave a Reply

Your email address will not be published. Required fields are marked *