简体   繁体   中英

How to cancel fragment navigation (using NavController)

I've used the default Android Studio 3.5 "Navigation Drawer" template for an app. That template uses the "new" (?) NavigationController for navigating between a set of sample fragments from the Navigation Drawer.

While I pretty much love the way it works, I'm looking for a way to prevent navigating to another fragment if the current fragment is dirty (for example: contains unsaved changes). So far I've not been able to figure out how to make that work. There don't seem to be any events that allow me to cancel navigation.

I've also tried to add another line to the following code generated by the template:

setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)

This sets up basic navigation between the fragments. Now, when I add this line:

navView.setNavigationItemSelectedListener(this)

I do get notified when another item is selected, but this disables the navigation at all, as the listener previously set by navView.setupWithNavController(navController) is replaced by this .

So maybe somebody could enlighten me as to whether there is a way of cancelling such a navigation under certain conditions without having to implement the fragment navigation myself?

PS: On a side note, I also need two items in the navigation drawer to actually open separate activities, which contradicts the "single activity" pattern propagated by Google, but is actually required in my case. Removing the line I added above of course leads to being unable to react to other items in the navigation drawer as expected.

Maybe the whole approach ist not suitable in my case?

This sounds like you want to implement Conditional Navigation .

That said, to implement what you ask, you can extend NavigationView to include a "preview" listener that fires first (Kotlin syntax):

package com.example.myapp

import android.content.Context
import android.util.AttributeSet
import android.view.MenuItem
import com.google.android.material.navigation.NavigationView

class CancellableNavigationView(context: Context, attrs: AttributeSet) :
    NavigationView(context, attrs) {
    private var cancellableListener =
        object : OnNavigationItemSelectedListener {
            var listener: OnNavigationItemSelectedListener? = null
            var prevListener: OnNavigationItemSelectedListener? = null

            override fun onNavigationItemSelected(item: MenuItem): Boolean {
                if (listener?.onNavigationItemSelected(item) == false) {
                    return false
                }
                return prevListener?.onNavigationItemSelected(item) ?: true
            }
        }

    override fun setNavigationItemSelectedListener(
        listener: OnNavigationItemSelectedListener?
    ) {
        cancellableListener.prevListener = listener
        super.setNavigationItemSelectedListener(cancellableListener)
    }

    fun setNavigationItemSelectedPreviewListener(
        listener: OnNavigationItemSelectedListener?
    ) {
        cancellableListener.listener = listener
    }
}

To use this, in your activity layout replace

...
    <com.google.android.material.navigation.NavigationView
        android:id="@+id/nav_view"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:fitsSystemWindows="true"
        app:headerLayout="@layout/nav_header_main"
        app:menu="@menu/activity_main_drawer" />
...

with the new class, keeping the attributes:

...
    <com.example.myapp.CancellableNavigationView
        android:id="@+id/nav_view"
        ... />
...

Then, in your activity, you can use the new preview listener to conditionally cancel the navigation:

...
class MainActivity : AppCompatActivity() {
    private lateinit var appBarConfiguration: AppBarConfiguration

    fun changesNeedSaving() : Boolean { ... }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        appBarConfiguration = AppBarConfiguration(TOP_LEVEL_NAV, drawer_layout)
        setupActionBarWithNavController(nc, appBarConfiguration)
        nav_view.setupWithNavController(nc)
        nav_view.setNavigationItemSelectedPreviewListener(
            NavigationView.OnNavigationItemSelectedListener { item ->
                Log.d(TAG, "got item: $item")
                !changesNeedSaving() // Conditionally allow navigation
            })
        ...
    }
    ...
}

Caveat: This will only work for the NavigationView part of the drawer, it will not let you cancel the navigation triggered by the NavController . For that, see the link to the Android Developer Guide at the top of this answer.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM