¿Hay alguna forma de mantener vivo el fragmento cuando se usa BottomNavigationView con el nuevo NavController?


96

Estoy intentando utilizar el nuevo componente de navegación. Yo uso un BottomNavigationView con navController: NavigationUI.setupWithNavController (bottomNavigation, navController)

Pero cuando cambio fragmentos, cada vez se destruyen / crean incluso si se usaron previamente.

¿Hay alguna manera de mantener vivo nuestro enlace de fragmentos principales a nuestro BottomNavigationView?


2
Según el comentario en issuetracker.google.com/issues/80029773 , parece que esto se resolverá eventualmente. Sin embargo, también me gustaría saber si la gente tiene una solución económica que no implique abandonar la biblioteca para que esto funcione mientras tanto.
Jimmy Alexander

Respuestas:


69

Prueba esto.

Navegador

Crea un navegador personalizado.

@Navigator.Name("custom_fragment")  // Use as custom tag at navigation.xml
class CustomNavigator(
    private val context: Context,
    private val manager: FragmentManager,
    private val containerId: Int
) : FragmentNavigator(context, manager, containerId) {

    override fun navigate(destination: Destination, args: Bundle?, navOptions: NavOptions?) {
        val tag = destination.id.toString()
        val transaction = manager.beginTransaction()

        val currentFragment = manager.primaryNavigationFragment
        if (currentFragment != null) {
            transaction.detach(currentFragment)
        }

        var fragment = manager.findFragmentByTag(tag)
        if (fragment == null) {
            fragment = destination.createFragment(args)
            transaction.add(containerId, fragment, tag)
        } else {
            transaction.attach(fragment)
        }

        transaction.setPrimaryNavigationFragment(fragment)
        transaction.setReorderingAllowed(true)
        transaction.commit()

        dispatchOnNavigatorNavigated(destination.id, BACK_STACK_DESTINATION_ADDED)
    }
}

NavHostFragment

Cree NavHostFragment personalizado.

class CustomNavHostFragment: NavHostFragment() {
    override fun onCreateNavController(navController: NavController) {
        super.onCreateNavController(navController)
        navController.navigatorProvider += PersistentNavigator(context!!, childFragmentManager, id)
    }
}

navigation.xml

Utilice una etiqueta personalizada en lugar de una etiqueta de fragmento.

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

    <custom_fragment
        android:id="@+id/navigation_first"
        android:name="com.example.sample.FirstFragment"
        android:label="FirstFragment" />
    <custom_fragment
        android:id="@+id/navigation_second"
        android:name="com.example.sample.SecondFragment"
        android:label="SecondFragment" />
</navigation>

diseño de actividad

Utilice CustomNavHostFragment en lugar de NavHostFragment.

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
        android:id="@+id/nav_host_fragment"
        android:name="com.example.sample.CustomNavHostFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@+id/bottom_navigation"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/navigation" />

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottom_navigation"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:menu="@menu/navigation" />

</androidx.constraintlayout.widget.ConstraintLayout>

Actualizar

Creé un proyecto de muestra. enlace

No creo NavHostFragment personalizado. Yo uso navController.navigatorProvider += navigator.


3
Si bien esta solución funciona ( fragmentsse reutiliza, no se vuelve a crear), hay un problema con la navegación. Cada menuitemen bottomnavigationviewse considera primaria - por lo tanto, cuando BACKse presiona, los acabados aplicación (o va a la componente de la parte superior). Una mejor solución sería comportarse como la aplicación de YouTube: (1) Toque Inicio; (2) Toque Tendencias; (3) Toque Bandeja de entrada; (4) Toque Tendencias; (5) Toque BACK: va a la Bandeja de entrada; (6) Toque BACK- va a Inicio; (7) Tap BACK: la aplicación existe. En resumen, la BACKfuncionalidad de YouTube entre " menuitems" se remonta a la más reciente, sin elementos repetidos.
miguelt

3
¿Cómo mantener la funcionalidad del botón Atrás? La aplicación está finalizando cuando se presiona el botón Atrás.
Francis

5
@ jL4, tuve el mismo problema, probablemente olvidó eliminar app:navGraphde NavHostFragment de su actividad.
Paul Chernenko

6
¿Por qué Google no elimina esta funcionalidad de fábrica?
Arsenio

55
NavigationComponent debería convertirse en una solución, no en otro problema, por lo que el programador debe crear una solución alternativa como esta.
Arie Agung

24

Vínculo de ejemplos de Google Simplemente copie NavigationExtensions en su aplicación y configúrelo con un ejemplo. Funciona genial.


1
Pero solo funciona para la navegación inferior. Si tiene una navegación general, tiene un problema
Georgiy Chebotarev

Estoy enfrentando el mismo problema y cada solución que verifico contiene código kotlin, pero me temo que uso Java en mi proyecto. ¿Alguien puede ayudarme a solucionar este problema en Java.
CodeRED Innovations

@CodeREDInnovations yo también, aunque había intentado y compilado con éxito con el archivo kotlin, la navegación se mantiene así: imgur.com/a/DcsKqPr no "reemplaza", además de eso, parece que la aplicación se vuelve más pesada y atascada.
Acauã Pitta

@ AcauãPitta ¿encontraste una solución en Java?
desarrollarKinberg

11

Después de muchas horas de investigación encontré una solución. Estuvo todo el tiempo justo frente a nosotros :) Hay una función: popBackStack(destination, inclusive)que navega a un destino dado si se encuentra en backStack. Devuelve booleano, por lo que podemos navegar allí manualmente si el controlador no encuentra el fragmento.

if(findNavController().popBackStack(R.id.settingsFragment, false)) {
        Log.d(TAG, "SettingsFragment found in backStack")
    } else {
        Log.d(TAG, "SettingsFragment not found in backStack, navigate manually")
        findNavController().navigate(R.id.settingsFragment)
    }

¿Cómo y dónde usar esto? Si navego del Fragmento A al B, luego De B a A, ¿cómo podría ir sin llamar a los métodos oncraeteView () y onViewCreated (), en resumen, sin reiniciar el fragmento A
Kishan Solanki

@UsamaSaeedUS: ¿Puede compartir el código, cómo usarlo? Como lo que menciona Kishan.
Code_Life

2

Si tiene problemas para pasar argumentos, agregue:

fragment.arguments = args

en la clase KeepStateNavigator


1

No disponible a partir de ahora.

Como solución alternativa, puede almacenar todos los datos obtenidos en el modelo de vista y tener esos datos disponibles cuando vuelva a crear el fragmento. Asegúrese de obtener la vista utilizando el contexto de actividades.

Puede usar LiveData para hacer que sus datos sean observables y conscientes del ciclo de vida


1
Esto es cierto, y data> view, pero el problema es cuando también desea mantener el estado de la vista, por ejemplo, varias páginas cargadas y el usuario se ha desplazado hacia abajo :).
Joaquim Ley

1

He utilizado el enlace proporcionado por @STAR_ZERO y funciona bien. Para aquellos que tienen problemas con el botón Atrás, pueden manejarlo en el host de actividad / navegación de esta manera.

override fun onBackPressed() {
        if(navController.currentDestination!!.id!=R.id.homeFragment){
            navController.navigate(R.id.homeFragment)
        }else{
            super.onBackPressed()
        }
    }

Simplemente verifique si el destino actual es su fragmento raíz / hogar (normalmente el primero en la vista de navegación inferior), si no, simplemente navegue de regreso al fragmento, si es así, solo salga de la aplicación o haga lo que quiera.

Por cierto, esta solución debe funcionar junto con el enlace de la solución anterior proporcionado por STAR_ZERO, utilizando keep_state_fragment .


idk por qué pero currentDestination !!. id siempre me da los mismos valores para los 3 fragmentos
Avishek Das

1

La solución proporcionada por @ piotr-prus me ayudó, pero tuve que agregar una verificación de destino actual:

if (navController.currentDestination?.id == resId) {
    return       //do not navigate
}

sin esta verificación, el destino actual se volverá a crear si navega por error hacia él, porque no se encontrará en la pila de actividades.


Me alegro de que mi solución te haya funcionado. De hecho, estoy revisando menuItem actual en bottomNavigationView antes de llamar al fragmento desde backStack usando: bottomNavigationView.setOnNavigationItemSelectedListener
Piotr Prus

0

Según la documentación de Android ,

Al crear NavHostFragment usando FragmentContainerView o si agrega manualmente NavHostFragment a su actividad a través de FragmentTransaction, el intento de recuperar NavController en onCreate () de una actividad a través de Navigation.findNavController (Actividad, @IdRes int) fallará. Debería recuperar NavController directamente de NavHostFragment.

Cambie su nav_host_fragment a -> FragmentContainerView y recupere el navController

    val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
    val navController = navHostFragment.navController

Puede conservar el estado del fragmento con componentes de navegación utilizando este enfoque

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.