Update selected state of navigation drawer after back press
Asked Answered
R

7

12

Whats the proper way to handle the selected state of the navigation drawer after back press?

I have a navigation drawer with n entries (in a listview) like the SDK sample in Android Studio. When i click on the navigation drawer entries i want them to be added to the back stack, so i can move back to them.

In onNavigationDrawerItemSelected(int pos) i have

        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        if (position == 0) {
            transaction.replace(R.id.container, new FragmentA());
        } else if (position == 1) {
            transaction.replace(R.id.container, new FragmentB());
        } else {
            transaction.replace(R.id.container, new FragmentC());
        }
        transaction.addToBackStack(null);
        transaction.commit();

When i click on the second entry in the drawer, B gets selected and replaces A. If i click the back button afterwards, Fragment A is shown again like it should, but B is still selected in the navigation drawer.

How can i get the selection status of the drawer updated after pressing back?

Somehow i need a call to mDrawerListView.setItemChecked(position, true); or NavigationDrawerFragment.selectItem(int position). But to which position? How do i remeber it?

Intercept with onBackPressed?

@Override
    public void onBackPressed() {}

But how do i know which fragment is active again? And to which position it corresponds.

Is there some easy solution i am blind to see? It seems that using back in combination with the navigation drawer and updating the selection status is a standard pattern.

Rivet answered 7/1, 2015 at 17:2 Comment(0)
V
8

This pattern is described in the Implement Back Navigation for Fragments section of the "Proper Back Navigation" documentation.

If your application updates other user interface elements to reflect the current state of your fragments, such as the action bar, remember to update the UI when you commit the transaction. You should update your user interface after the back stack changes in addition to when you commit the transaction. You can listen for when a FragmentTransaction is reverted by setting up a FragmentManager.OnBackStackChangedListener:

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

That would be the proper place to refresh the current option in the navigation drawer.

Vidette answered 7/1, 2015 at 17:48 Comment(2)
This answers only the question when to update. I could also use onBackPressed(). The main question is which position should be highlighted in the drawer? I probably need to get the old, now "active" fragment and a mapping fragment->position reverse of that in onNavigationDrawerItemSelected. Maybe store the position as request code with setTargetFragment?Rivet
@Rivet Yes, that would work, and then notify the drawer in the Fragment's onCreateView(). Nevertheless, I would still suggest using a listener instead of onBackPressed(). For example, pressing Back may not actually change the current fragment (e.g. dismissing a search view).Vidette
M
7

today I had the same strugle. I solved it by implementing an Interface FragmentToActivity in every Fragment and in my one and only MainActivity.

Interface:

public interface FragmentToActivity {
    public void SetNavState(int id);
}

Fragments:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    ((FragmentToActivity) getActivity()).SetNavState(R.id.nav_myitemid); //just add this line
}

MainActivity:

@Override
public void SetNavState(int id) {
    NavigationView nav = (NavigationView) findViewById(R.id.nav_view);
    nav.setCheckedItem(id);
}

And if you have no idea, where R.id.nav_myitemid comes from, here is my activity_main_drawer.xml:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">    
    <group android:checkableBehavior="single">
        <item
            android:id="@+id/nav_myitemid"
            android:title="@string/home" />
    </group>
</menu>
Maurice answered 3/2, 2017 at 14:52 Comment(3)
Great! but i have one question, why you used interface function instead of a normal public function?Ornithopod
@Ornithopod He need callback,setCheckedItem only can be done from parent activity instead of Fragment,Gegenschein
Good and clean!Thaine
B
5

I have had alot of trouble trying to figure out a solution to this problem and the solution I ended up using isn't "nice" in my opinion, but it works.

In my application i ended up adding a fragments position in the navigation drawer, when i added the fragment to the backstack. This way i could call setItemChecked with the position in my onBackStackChanged(). So basicly what i did was this:

@Override
public void onBackStackChanged() {
    FragmentManager fm = getFragmentManager();
    String name = fm.getBackStackEntryAt(fm.getBackStackEntryCount()-1).getName();
    mDrawerList.setItemChecked(Integer.parseInt(name),true);
}

public class DrawerItemClickListener implements android.widget.AdapterView.OnItemClickListener {
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        selectItem(position);
    }
}

private void selectItem(int position) {
    Fragment fragment;

    switch (position) {
        case 0: // Home
            fragment = new ContentFragment();
            break;
        case 1: // Another
           fragment = new AnotherFragment();
            break;
        default: // Logout
        {
            finish();

            Intent intent = new Intent(this, StartupActivity.class);
            startActivity(intent);

            return;
        }
    }

    FragmentManager fragmentManager = getFragmentManager();
    fragmentManager.beginTransaction()
            .replace(R.id.content_frame, fragment)
            .addToBackStack(Integer.toString(position))
            .commit();

    mDrawerList.setItemChecked(position, true);
    setTitle(mNavigationItems[position]);
    mDrawerLayout.closeDrawer(mDrawerList);
}
Buffum answered 27/3, 2015 at 13:13 Comment(1)
Thanks a lot :) had to spend enough time with ugly way until found your answer. :)Doha
O
3

Building upon @matiash 's excellent answer (be sure to +1 his/hers if you +1 my answer)

To start, we're assuming we're in the Activity

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

You can find the current fragment with the following:

(from https://mcmap.net/q/73592/-how-do-i-get-the-currently-displayed-fragment)

Fragment f = getFragmentManager().findFragmentById(R.id.frag_container);
if (f instanceof CustomFragmentClass) {
    // do something with f
    f.doSomething();
}

That should get you a step closer. Then something like this will get you a reference to your nav drawer:

nav = (NavigationDrawerFragment) getFragmentManager().findFragmentById(R.id.nav);

Then depending how you setup your drawer fragment code:

nav.updateSelected(position);

The updateSelected(int) method would be in your NavigationDrawerFragment class and might look something like this:

public void updateSelected(int position) {
    mCurrentSelectedPosition = position;
    if (mDrawerListView != null) {
        mDrawerListView.setItemChecked(position, true);
    }
}

NOTE: there are many ways to do this, but this should get you started.

Oxley answered 27/5, 2015 at 21:41 Comment(0)
S
1

It's actually simple. You already have your navigation view in Drawer layout. For each item pressed, it opens the respective fragment, right? Now go the onCreateView of each fragment and do this...

NavigationView navigationView = getActivity().findViewById(R.id.navigation_view);
        navigationView.setCheckedItem(R.id.this_fragment's_item);

Also, In your Main Activity where you're handling item click, use an if statement such that

if (navigation_view.getMenu().findItem(R.id.account_nav).isChecked())   
  drawer_layout.closeDrawer(GravityCompat.START);

The above avoids creating multiple instances of a fragment on top of itself.

Simulation answered 24/2, 2021 at 16:5 Comment(1)
This is the best solutionPositron
S
0

This is late but for those who are using navigation menu instead of list, here is how i did it

@SuppressWarnings("StatementWithEmptyBody")
    @Override
    public boolean onNavigationItemSelected(MenuItem item) {
        // Handle navigation view item clicks here.
       int position=0;
       Fragment fragment;
        int id = item.getItemId();
        if (id == R.id.nav_home) {
            fragment=new HomeFragment();
            position=0;

        } else if (id == R.id.nav_profile) {

           fragment=new ProfileFragment();
            position=1;
        }  else if (id == R.id.nav_settings) {
             fragment=new SettingsFragment();
            position=2;
        }

          getSupportFragmentManager()
            .beginTransaction()
            .add(R.id.relative_container, fragment)
            .addToBackStack(""+position)
            .commit();

        DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
        drawer.closeDrawer(GravityCompat.START);
        return true;
    }

and add listner for backstack

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

             FragmentManager fm = getSupportFragmentManager();
             String name = fm.getBackStackEntryAt(fm.getBackStackEntryCount()-1).getName();
               if(!TextUtils.isEmpty(name))
             selectNavigationDrawerItem(Integer.parseInt(name));

        }
    });

selectNavigationDrawerItem method

 private void selectNavigationDrawerItem(int position){

               if(position==0){
                   navigationView.setCheckedItem(R.id.nav_home);
               }
               else if(position==1){
                   navigationView.setCheckedItem(R.id.nav_profile);
               }
               else if(position==2){
                   navigationView.setCheckedItem(R.id.nav_settings);   
              }

     }
Spherics answered 4/9, 2017 at 7:21 Comment(0)
C
0

Here is a solution in kotlin.

In the fragment that you want to be selected and highlighted by the drawer, you simply do:

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

    val navView: NavigationView = activity!!.findViewById(R.id.nav_view)
    navView.setCheckedItem(R.id.name_of_this_fragment)

    ...
}
Coblenz answered 17/5, 2020 at 22:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.