Navigation Drawer back button in fragments
Asked Answered
S

7

11

I started creating of app which use one activity (Navigation Drawer) and many fragments. But I unable to use toolbar back button to navigate back from fragments. Hardware back button works perfectly. I know that I need to override onOptionsItemSelected, catch android.R.id.home, check if there are something in back stack and than pop it. After changing fragment, "burger" button changes to "back arrow", but when I click on it onOptionsItemSelected never fired, just opens the NavigationDrawer menu.

Here the code from activity:

public class NavDrawerActivity extends AppCompatActivity implements ... {

    NavigationView navigationView;
    BottomNavigationView bottomNavigationView;
    ActionBarDrawerToggle toggle;
    FragmentManager fragmentManager;

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

        fragmentManager = getSupportFragmentManager();

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

        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.addDrawerListener(toggle);

        toggle.syncState();

        // Set back button
        fragmentManager.addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
            @Override
            public void onBackStackChanged() {
                if (fragmentManager.getBackStackEntryCount() > 0) {
                    getSupportActionBar().setDisplayHomeAsUpEnabled(true);
                } else {
                    getSupportActionBar().setDisplayHomeAsUpEnabled(false);
                    toggle.syncState();
                }
            }
        });

        // Load default fragment
        changeFragment(new HomeFragment(), false);

        navigationView = (NavigationView) findViewById(R.id.nav_view);
        navigationView.setNavigationItemSelectedListener(this);

        View headerLayout = navigationView.getHeaderView(0);

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

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                Toast.makeText(this, "Back pressed", Toast.LENGTH_SHORT)
                        .show();
                onBackPressed();
                return true;
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onBackPressed() {
        DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);

        if (drawer.isDrawerOpen(GravityCompat.START))
            drawer.closeDrawer(GravityCompat.START);

        if (drawer.isDrawerOpen(GravityCompat.START)) {
            drawer.closeDrawer(GravityCompat.START);
        } else if (fragmentManager.getBackStackEntryCount() > 0) {
            fragmentManager.popBackStack();
        } else {
            super.onBackPressed();
        }
    }

    private void changeFragment(Fragment fm, boolean addToBackStack)
    {
        FragmentTransaction ft = fragmentManager.beginTransaction();
        ft.replace(R.id.frame_layout_content, fm);
        ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
        if (addToBackStack) ft.addToBackStack(null);
        ft.commit();
    }

}

And how I change (replace) fragments from HomeFragment:

IndexDetailFragment newFragment = new IndexDetailFragment();
Bundle args = new Bundle();

args.putString(IndexDetailFragment.ARG_INDEX_ID, id);

newFragment.setArguments(args);

FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left);
transaction.replace(R.id.frame_layout_content, newFragment);
transaction.addToBackStack(null);

transaction.commit();
Sidonie answered 10/1, 2018 at 10:57 Comment(2)
This is just a guess, you might need to overwrite onCreateOptionsMenu()Circumbendibus
onCreateOptionsMenu already overrided and returns true.Sidonie
B
12

setNavigationOnClick() on the toolbar after setSupportActionBar(toolbar) :

setSupportActionBar(toolbar);
toolbar.setNavigationOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View view) {
                Toast.makeText(getActivity(), "Back clicked!",     
                Toast.LENGTH_SHORT).show();
            }
        });

I have made a small app for reference

FirstFragment

 public class FirstFragment extends Fragment {


public FirstFragment() {
    // Required empty public constructor
}


@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    return inflater.inflate(R.layout.fragment_first, container, false);
}

}

SecondFragment

public class SecondFragment extends Fragment {


public SecondFragment() {
    // Required empty public constructor
}


@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    return inflater.inflate(R.layout.fragment_second, container, false);
}

}

MainActivity

public class MainActivity extends AppCompatActivity {
private Toolbar mToolbar;
private ActionBarDrawerToggle drawerToggle;
private DrawerLayout mDrawerLayout;
private String TAG = "MainActivity";
private FragmentManager mFragmentManager;
private NavigationView mNavigationView;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mToolbar = (Toolbar) findViewById(R.id.toolbar);
    if (mToolbar != null) {
        setSupportActionBar(mToolbar);
    }
    getSupportActionBar().setDisplayHomeAsUpEnabled(true);

    mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer);
    drawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, R.string.drawer_open, R.string.drawer_close) {
        @Override
        public void onDrawerOpened(View drawerView) {
            super.onDrawerOpened(drawerView);
        }

        @Override
        public void onDrawerClosed(View drawerView) {
            super.onDrawerClosed(drawerView);
        }
    };
    mDrawerLayout.addDrawerListener(drawerToggle);
    mNavigationView = findViewById(R.id.navigation);
    mNavigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
        @Override
        public boolean onNavigationItemSelected(@NonNull MenuItem item) {
            switch (item.getItemId()) {
                case R.id.first:
                    changeFragment(new FirstFragment(), true);
                    return true;
                case R.id.second:
                    changeFragment(new SecondFragment(), true);
                    return true;
            }
            return false;
        }
    });
    mFragmentManager = getSupportFragmentManager();
    changeFragment(new FirstFragment(), true);
}

@Override
protected void onPostCreate(@Nullable Bundle savedInstanceState) {
    super.onPostCreate(savedInstanceState);
    if (drawerToggle != null) {
        drawerToggle.syncState();
    }
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    if (item.getItemId() == android.R.id.home) {
        Log.i(TAG, "onOptionsItemSelected: Home Button Clicked");
        if (mDrawerLayout.isDrawerOpen(Gravity.START)) {
            mDrawerLayout.closeDrawer(Gravity.START);
        } else {
            mDrawerLayout.openDrawer(Gravity.START);
        }
    }
    return super.onOptionsItemSelected(item);
}

@Override
public void onBackPressed() {
    if (mDrawerLayout.isDrawerOpen(Gravity.START)) {
        mDrawerLayout.closeDrawer(Gravity.START);
    }
    if (mDrawerLayout.isDrawerOpen(Gravity.START)) {
        mDrawerLayout.closeDrawer(Gravity.START);
    } else if (mFragmentManager.getBackStackEntryCount() > 0) {
        mFragmentManager.popBackStack();
    } else {
        super.onBackPressed();
    }
}

private void changeFragment(Fragment fragment, boolean needToAddBackstack) {
    FragmentTransaction mFragmentTransaction = mFragmentManager.beginTransaction();
    mFragmentTransaction.replace(R.id.FRAME_CONTENT, fragment);
    mFragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
    if (needToAddBackstack)
        mFragmentTransaction.addToBackStack(null);
    mFragmentTransaction.commit();
}
}

activity_main

<FrameLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">
            

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="@dimen/abc_action_bar_default_height_material"
        android:background="@color/colorPrimaryDark"
        />

    <FrameLayout
        android:id="@+id/FRAME_CONTENT"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="@dimen/abc_action_bar_default_height_material" />
</FrameLayout>

<android.support.design.widget.NavigationView
    android:id="@+id/navigation"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:layout_gravity="start"
    android:layout_marginTop="@dimen/abc_action_bar_default_height_material"
    app:menu="@menu/drawermenu" />
  </android.support.v4.widget.DrawerLayout>
Bellow answered 10/1, 2018 at 11:19 Comment(2)
@Sidonie I've created a sample for the reference and Its working perfectly fine. Check edited answer.Bellow
Thanks for reply. I resolved an issue by reading some documentation of ActionBarDrawerToggle. We should use constructor without Toolbar parameter: public ActionBarDrawerToggle(Activity activity, DrawerLayout drawerLayout, int openDrawerContentDescRes, int closeDrawerContentDescRes)Sidonie
K
4

I had the same issue.

I finally solved by adding this in the onCreate method.

getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
        @Override
        public void onBackStackChanged() {
            if(getSupportFragmentManager().getBackStackEntryCount() == 0){
                drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
                menuToggle.setDrawerIndicatorEnabled(true);
            }else{
                drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
                menuToggle.setDrawerIndicatorEnabled(false);

            }
        }
    });

Hope it helps

Kairouan answered 24/3, 2018 at 20:12 Comment(0)
X
1

Simplest solution for Kotlin Developers

Just add this in your root activity where fragments resist

if (supportFragmentManager.backStackEntryCount > 0) {
            supportActionBar!!.setDisplayHomeAsUpEnabled(true)
            toolbar.setNavigationOnClickListener {
                if (supportFragmentManager.backStackEntryCount > 0) {
                    super.onBackPressed()
                } else {
                    supportActionBar!!.setDisplayHomeAsUpEnabled(false)
                    drawerLayout.addDrawerListener(toggle)
                    toggle.syncState()
                    drawerLayout.openDrawer(GravityCompat.START)
                }
            }
        } else {
            supportActionBar!!.setDisplayHomeAsUpEnabled(false)
            drawerLayout.addDrawerListener(toggle)
            toggle.syncState()
        }

Here, whenever setDisplayHomeAsUpEnabled is set true , I am showing back button. and on cliking it, I am calling super.onBackPressed() which is similar to what your back button does!

Xuanxunit answered 17/5, 2020 at 15:19 Comment(0)
F
0

Add below code in onCreate() method that's work for me

    getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
        @Override
        public void onBackStackChanged() {
            if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
                getSupportActionBar().setDisplayHomeAsUpEnabled(true);
                toolbar.setNavigationOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        Toast.makeText(MainActivity.this, " Back Pressed ", Toast.LENGTH_SHORT).show();
                    }
                });
            } else {
                getSupportActionBar().setDisplayHomeAsUpEnabled(false);
                drawerToggle = new ActionBarDrawerToggle(MainActivity.this, drawerLayout, toolbar,
                        R.string.app_name, R.string.app_name);
                drawerLayout.addDrawerListener(drawerToggle);
                drawerToggle.syncState();
            }
        }
    });
Flor answered 1/12, 2019 at 7:3 Comment(0)
C
0

The thing that solved me the issue was this:

I used the code that came automatically with the NavigationDrawerActivity template, and it was something like this:

    NavigationView navigationView = _binding.navView;
    // Passing each menu ID as a set of Ids because each menu should be considered as top level destinations:
    _AppBarConfiguration = new AppBarConfiguration.Builder(R.id.nav_x, R.id.nav_y, R.id.nav_z)
            .setOpenableLayout(_binding.drawerLayout)
            .build();
    _navController = Navigation.findNavController(this, R.id.nav_host_content_main);
    NavigationUI.setupActionBarWithNavController(this, _navController, _AppBarConfiguration);
    NavigationUI.setupWithNavController(navigationView, _navController);

And I struggled a lot with many tricks and hacks, and nothing was working for me.

At some point, I have noticed that the fragments that I want to enable the back button are "being considered as top level destinations", exactly what the comment says in the code above. Then I removed those sub-fragments and BOOM that was it, with not a single hack or line of code needed, this is provided out of the box, I just needed to pay attention to it.

So in my case, I changed to the below code:

NavigationView navigationView = _binding.navView;
// Passing each menu ID as a set of Ids because each menu should be considered as top level destinations:
_AppBarConfiguration = new AppBarConfiguration.Builder(R.id.nav_x)
        .setOpenableLayout(_binding.drawerLayout)
        .build();
_navController = Navigation.findNavController(this, R.id.nav_host_content_main);
NavigationUI.setupActionBarWithNavController(this, _navController, _AppBarConfiguration);
NavigationUI.setupWithNavController(navigationView, _navController);

: Just removed R.id.nav_y, R.id.nav_z in the AppBarConfiguration

Counterchange answered 5/12, 2021 at 13:38 Comment(1)
I got a similar issue but I have a modularized app architecture. My feature modules provide their navigation graph and I was passing the id of their navigation graph as top level destinations. And I never saw the back button when I was navigating deeper in the navigation graph of some of these feature modules. What I had to do was to give the id of the first fragment instead of the id of the nav graph, then the back arrow appeared for the destinations that are not top level.Monarchist
A
0

I struggled with this for a while. I got it to work by:

  1. setting my the drawer layout in MainActivity.kt:
    val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout)
         val navHostFragment =
        supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment

    val navController = navHostFragment.navController
    findViewById<NavigationView>(R.id.nav_view).setupWithNavController(navController)
        appBarConfiguration = AppBarConfiguration(navController.graph, drawerLayout)
  1. Include the toolbar layout in each of the fragments that I need the Toolbar:
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">

    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            />

</com.google.android.material.appbar.AppBarLayout>
</LinearLayout>
  1. Add the following code to the home fragment of the drawer, the one from which other fragments are called using the drawer. This gets the hamburguer icon to work, onside the onViewCreated method:
    var myToolbar = requireActivity()
            .findViewById<androidx.appcompat.widget.Toolbar>(R.id.toolbar)
        myToolbar.inflateMenu(R.menu.menu)
    
     val drawerLayout = requireActivity().findViewById<DrawerLayout>(R.id.drawer_layout)
        val toggle = ActionBarDrawerToggle(
            activity, drawerLayout, myToolbar,
            R.string.navigation_drawer_open, R.string.navigation_drawer_close
        )
        toggle.setDrawerIndicatorEnabled(true)
        drawerLayout.addDrawerListener(toggle)
        toggle.syncState()
    
    
    
  2. Add the following code in the kotlin code for the fragments that are after the home fragment, this will get the back arrow:
      val navController = findNavController()
        val appBarConfiguration = AppBarConfiguration(navController.graph)

        view.findViewById<Toolbar>(R.id.toolbar)
            .setupWithNavController(navController, appBarConfiguration)
  1. In the destination fragment add the following code in onCreateView Method:
      activity?.onBackPressedDispatcher?.addCallback(
            viewLifecycleOwner, object : OnBackPressedCallback(true) {
                override fun handleOnBackPressed() {
         
                    findNavController().navigate(
                        [back fragment directions] )
                }
            })

Hope this helps everyone struggling with this :)

Anesthetist answered 10/12, 2021 at 18:43 Comment(0)
G
0

For the people struggling with the same issue in Kotlin, this is what worked for me. I was using the following override code for onSupportNavigateUp() :

override fun onSupportNavigateUp(): Boolean {
    val navHostFragment = supportFragmentManager.findFragmentById(R.id.your_nav_host_fragment) as NavHostFragment
    val navController = navHostFragment.navController

    return navController.navigateUp() || super.onSupportNavigateUp()
}

Then i replaced it by:

override fun onSupportNavigateUp(): Boolean {
    val navHostFragment = supportFragmentManager.findFragmentById(R.id.your_nav_host_fragment) as NavHostFragment
    val navController = navHostFragment.navController

    return when {
        binding.drawerLayout.isDrawerOpen(GravityCompat.START) -> {
            binding.drawerLayout.closeDrawer(GravityCompat.START)
            true
        }
        navController.currentDestination?.id == R.id.homeFragment -> {
            binding.drawerLayout.openDrawer(GravityCompat.START)
            true
        }
        else -> navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
    }
}

AppBarConfiguration code (activity class):

private lateinit var appBarConfiguration: AppBarConfiguration

onCreate():

    setSupportActionBar(binding.toolbar)
    supportActionBar?.setDisplayHomeAsUpEnabled(true)
    val navHostFragment = supportFragmentManager.findFragmentById(R.id.your_nav_host_fragment) as NavHostFragment
    val navController = navHostFragment.navController

    // Fragments that must display the hamburger icon (top level destinations)
    appBarConfiguration = AppBarConfiguration(
        setOf(
            R.id.homeFragment
        ), binding.drawerLayout
    ) 

    setupActionBarWithNavController(navController, appBarConfiguration)
Gregoriagregorian answered 9/8 at 13:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.