简体   繁体   中英

Android Navigation Component with Bottom Navigation doesn't destroy startDestination Fragment

I've set up bottom navigation with nav graph , in the most basic way -

NavigationUI.setupWithNavController(bottomNavigationView, navHostFragment.navController)

the fragment that's declared as startDestination is never destroyed when navigating from it (only paused) while all other fragments are destroyed when navigating away.

(I need it to be destroyed so that in the viewModel associated with it onCleared() will be called).

Any idea why? or How to change this behavior?

navigation:

<navigation
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/navigation"
app:startDestination="@id/drawingFragment">

<fragment
    android:id="@+id/controllerFragment"
    android:name="com.example.android.myApp.ControllerFragment"
    android:label="fragment_controller"
    tools:layout="@layout/fragment_controller" >
    <action
        android:id="@+id/action_controllerFragment_to_drawingFragment"
        app:destination="@id/drawingFragment" />
</fragment>
<fragment
    android:id="@+id/drawingFragment"
    android:name="com.example.android.myApp.DrawingFragment"
    android:label="fragment_drawing"
    tools:layout="@layout/fragment_drawing" >
    <action
        android:id="@+id/action_drawingFragment_to_clippingFragment"
        app:destination="@id/clippingFragment"
        app:launchSingleTop="true"
        app:popUpTo="@+id/drawingFragment"
        app:popUpToInclusive="true" />
</fragment>
<fragment
    android:id="@+id/clippingFragment"
    android:name="com.example.android.myApp.ClippingFragment"
    android:label="fragment_clipping"
    tools:layout="@layout/fragment_clipping" />

MainActivity:

class MainActivity : AppCompatActivity() {

private lateinit var navHostFragment: NavHostFragment
private lateinit var bottomNavigationView: BottomNavigationView


override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    setUpNavigation()
}

fun setUpNavigation(){
    bottomNavigationView = findViewById(R.id.bttm_nav)
     navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment

    NavigationUI.setupWithNavController(bottomNavigationView, navHostFragment.navController)}

activity_main/xml:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <fragment
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:navGraph="@navigation/navigation" />

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bttm_nav"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:itemTextAppearanceActive="@style/bottomNaActive"
        app:itemTextAppearanceInactive="@style/bottomNavInactive"
        app:layout_constraintBottom_toBottomOf="@+id/nav_host_fragment"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:menu="@menu/bottom_menu_nav" />
</androidx.constraintlayout.widget.ConstraintLayout>

this is actually no answer rather than a comment on @isrzanza's answer (sorry, not enough reputation).

I assumed in case of BottomNavigationView there is no up or down . For me each fragment which I can navigate from here a more like neighbors respectively equal important so I thought not only the ViewModels of these were cleared rather I asssumed that the fragment itself would be destroyed via onDestroy when navigating away .

I don't know why specially the start fragment should stay in memory and the others don't, because they are equal important, didn't they?


EDIT:

I also want to mention I noticed if I navigate to the startDestination-fragment again a new fragment of same type is created (onCreate will be executed again) and the old one will be destroyed (onDestroy will be executed). For me this is a other waste in resources. To keep the fragment for this situation in memory and re-create it afterwards anyway makes no sense to me. Hope I misunderstood something here :)

What you are describing is the default behavior of the navigation component. when navigating down , the fragment you navigated from is not destroyed, only when navigating up .

personally I don't understand why you would want to notify the viewModel the fragment is destroyed, but if you want to run a certain piece of code when you are navigating to another destination, you could use NavController.OnDestinationChangedListener in your main activity (or in your fragment, but don't forget to remove the listener when it is destroyed), and do some action according to your start and end destinations.

if you want to destroy the fragment anyway you can try changing the "pop to" parameter in the navigation action in your nav graph.

@chrgue described the problem correctly

In case of "top level" navigation, there is no concept of is no up or down . But startDestination fragment is kept in memory, and, at the same time it is recreated when switching to it.

What is especially unpleasant if the application only contains "top level" fragments.

I do not know how to properly solve this problem. For myself , I writed the next code.

The code turned out to be complicated because I had to solve problems: the back button did not work correctly and did not recover after OOM (also action navigation with "pop to"), and it is a terrible hack

extension method

fun FragmentActivity.enableDestroyStartDestination(
    navController: NavController,
    appBarConfiguration: AppBarConfiguration
) {
    val startDestinationId = navController.graph.startDestination

    var firstStart = true
    var preventMainRecursionFlag = false
    var latestDestionationIsMain = false
    navController.addOnDestinationChangedListener { controller, destination, args ->
        if (appBarConfiguration.topLevelDestinations.contains(destination.id)) {
            if (destination.id == startDestinationId) {
                latestDestionationIsMain = true
                if (firstStart) {
                    firstStart = false
                    return@addOnDestinationChangedListener
                }
                if (preventMainRecursionFlag) {
                    preventMainRecursionFlag = false
                    return@addOnDestinationChangedListener
                }
                preventMainRecursionFlag = true
                val options = NavOptions.Builder().setLaunchSingleTop(true).build()
                controller.navigate(startDestinationId, args, options)
            } else {
                val navHostFragment =
                    supportFragmentManager.primaryNavigationFragment as NavHostFragment
                if (navHostFragment.childFragmentManager.fragments.size > 0) {
                    if (latestDestionationIsMain) {
                        val fragment = navHostFragment.childFragmentManager.fragments[0]
                        navHostFragment.childFragmentManager.beginTransaction().remove(fragment)
                            .commitNowAllowingStateLoss()
                    }
                }
                latestDestionationIsMain = false
            }
        } else {
            latestDestionationIsMain = false
        }
    }
}

usage (Navigation Drawer or BottomNavigationView)

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    ...

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

        appBarConfiguration = AppBarConfiguration(
            setOf(...    ), drawerLayout
        )
        setupActionBarWithNavController(navController, appBarConfiguration)
        navView.setupWithNavController(navController)

        this.enableDestroyStartDestination(navController, appBarConfiguration)
    }

I can't guarantee it will work anywhere - my application is too simple.

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