Home > Mobile >  Why does navigation not work in the Navigation Drawer Activity template with version 2.4.1?
Why does navigation not work in the Navigation Drawer Activity template with version 2.4.1?

Time:03-22

(Using Android Studio 2021.1.1)

Creating a new project using the Navigation Drawer Activity:

  1. Created a default android application with the Navigation Drawer Activity template.
  2. Added a Settings Fragment to the project to test the action_settings menu and config menu items.
  3. Overridded onOptionsItemSelected() in MainActivity.java to handle the settings menu like so:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
    Bundle bundle = new Bundle();
        switch (item.getItemId()) {
            case R.id.action_settings:
                Navigation
                    .findNavController(this, R.id.nav_host_fragment_content_main)
                    .navigate(R.id.nav_settings);
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }
}

Testing:

Run the project, the drawer menu works fine and opens fragments as expected. The problem is that when you click on the overflow menu to open the settings fragment it works, but the drawer menu doesn't work anymore when opening the home fragment.

enter image description here
enter image description here

Observation:

After some testing, I found that it's because of the dependency version, downgrading it to 2.3.5 from 2.4.1 resolves the issue.

Is there something wrong with my code or is it because of an API change? How do I handle this without downgrading?

Extra info:

In MainActivity's onCreate() method I added the following:

     mAppBarConfiguration = new AppBarConfiguration.Builder(
                R.id.nav_home, R.id.nav_gallery, R.id.nav_slideshow, R.id.nav_settings)
                .setOpenableLayout(drawer)
                .build();

app module's build.gradle:

plugins {
    id 'com.android.application'
}

android {
    compileSdk 31

    defaultConfig {
        applicationId "com.example.myapplication"
        minSdk 23
        targetSdk 31
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 
            'proguard-rules.pro'
        }
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    buildFeatures {
        viewBinding true
    }

    buildToolsVersion '32.0.0'
        ndkVersion '23.1.7779620'
    }

    dependencies {
        implementation 'androidx.appcompat:appcompat:1.4.1'
        implementation 'com.google.android.material:material:1.5.0'
        implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
        implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1'
        implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1'
        implementation 'androidx.navigation:navigation-fragment:2.4.1'
        implementation 'androidx.navigation:navigation-ui:2.4.1'
        implementation 'androidx.legacy:legacy-support-v4:1.0.0'
        
        testImplementation 'junit:junit:4.13.2'
        androidTestImplementation 'androidx.test.ext:junit:1.1.3'
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
    }
}

CodePudding user response:

tl/dr: You should be following the Tie destinations to menu items documentation and using NavigationUI.onNavDestinationSelected() to get the right behavior:

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    NavController navController = Navigation.findNavController(this,
        R.id.nav_host_fragment_content_main);

    // By calling onNavDestinationSelected(), you always get the right behavior
    return NavigationUI.onNavDestinationSelected(item, navController)
            || super.onOptionsItemSelected(item);
}

Why

Navigation 2.4 uses multiple back stacks associated with each element in your NavigationView as per the Add a navigation drawer guide:

Starting in Navigation 2.4.0-alpha01, the state of each menu item is saved and restored when you use setupWithNavController.

That means that the 'Home' screen has a back stack associated with it that is restored when you tap on that icon, same for Gallery, Slideshow, and Settings. This is how the state is saved for that item.

This means that each tap on the items in your drawer isn't just navigating to that screen, but is swapping in the entire back stack associated with that item - everything that you've navigated to from that first screen.

So when you call Navigation.findNavController(this, R.id.nav_host_fragment_content_main).navigate(R.id.nav_settings);, you aren't doing the same thing as selecting the Settings item in your drawer - you're just adding the Settings screen to the 'Home' screen's back stack. This is why tapping on the Home icon doesn't do anything - you're already on the 'Home' screen's back stack.

What you actually want to do is swap to the entirely separate back stack associated with the nav_settings item. This would separate the nav_settings back stack from the Home back stack, thus ensuring that tapping the Home icon takes you back to the Home screen's back stack.

This is exactly what the NavigationUI.onNavDestinationSelected() API does (as that's exactly what the setupWithNavController API uses), so you can simply use that directly in your onOptionsItemSelected().

However, if you want to manually call navigate() (which, by the way, means you aren't getting the cross fade animation that you get by default when using onNavDestinationSelected), you can add the saving state flags to your navigate call by applying NavOptions programmatically:

@Override
 public boolean onOptionsItemSelected(MenuItem item) {
    Bundle bundle =new Bundle();
    switch (item.getItemId()) {
        case R.id.action_settings:
        {
            // Manually build the NavOptions that manually do
            // what NavigationUI.onNavDestinationSelected does for you
            NavOptions navOptions = new NavOptions.Builder()
                .setPopUpTo(R.id.nav_home, false, true)
                .setRestoreState(true)
                .build();

            NavController navController = Navigation.findNavController(this, 
                R.id.nav_host_fragment_content_main);

            navController.navigate(R.id.nav_settings, navOptions);
            return true;
        }

        default:
            return super.onOptionsItemSelected(item);
    }
}

Note that the setupWithNavController API relies on the nested graph of the current destination to determine which item is selected - the expectation is that all destinations in the 'Home' tab are part of the 'Home' navigation graph. So because you've swapped to the nav_settings, setupWithNavController assumes you've swapped to that back stack. Since you haven't actually done that, that's why your selected item gets out of sync with which back stack you are on.

  • Related