Czy istnieje sposób na utrzymanie fragmentu przy życiu podczas korzystania z BottomNavigationView z nowym NavController?

101

Próbuję użyć nowego komponentu nawigacji. Używam BottomNavigationView z navController: NavigationUI.setupWithNavController (bottomNavigation, navController)

Ale kiedy zmieniam fragmenty, za każdym razem niszczą / tworzą, nawet jeśli były wcześniej używane.

Czy istnieje sposób, aby utrzymać przy życiu nasze główne fragmenty, które prowadzą do naszego widoku BottomNavigationView?

IdAndro
źródło
3
Zgodnie z komentarzem na issuetracker.google.com/issues/80029773 wydaje się, że problem zostanie ostatecznie rozwiązany. Byłbym również ciekawy, czy ludzie mają tanie obejście, które nie wymaga opuszczania biblioteki, aby tymczasowo to zadziałało.
Jimmy Alexander

Odpowiedzi:

72

Spróbuj tego.

Nawigator

Utwórz nawigator niestandardowy.

@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

Utwórz niestandardowy NavHostFragment.

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

navigation.xml

Użyj tagu niestandardowego zamiast tagu fragmentowego.

<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>

układ zajęć

Użyj CustomNavHostFragment zamiast 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>

Aktualizacja

Stworzyłem przykładowy projekt. połączyć

Nie tworzę niestandardowego NavHostFragment. Używam navController.navigatorProvider += navigator.

STAR_ZERO
źródło
3
Chociaż to rozwiązanie działa ( fragmentssą ponownie używane, a nie odtwarzane), występuje problem z nawigacją. Każdy menuitemz nich bottomnavigationviewjest uważany za podstawowy - dlatego po BACKnaciśnięciu aplikacja kończy pracę (lub przechodzi do górnego komponentu). Lepszym rozwiązaniem byłoby zachowywanie się tak, jak w aplikacji YouTube: (1) Dotknij ekranu głównego; (2) Dotknij Trendy; (3) Dotknij Skrzynka odbiorcza; (4) Dotknij Trendy; (5) Dotknij BACK- przechodzi do skrzynki odbiorczej; (6) Dotknij BACK- przechodzi do domu; (7) Tap BACK- aplikacja istnieje. Podsumowując, BACKfunkcja YouTube między „ menuitems” wraca do najnowszej, bez powtarzania elementów.
miguelt
3
Jak zachować funkcjonalność przycisku Wstecz? Aplikacja kończy pracę po naciśnięciu przycisku Wstecz.
Francis
5
@ jL4, miałem ten sam problem, prawdopodobnie zapomniałeś usunąć app:navGraphze swojej aktywności element NavHostFragment.
Paul Chernenko,
7
Dlaczego Google nie oferuje tej funkcji po wyjęciu z pudełka?
Arsenius
61
NavigationComponent powinien stać się rozwiązaniem, a nie kolejnym problemem, więc programista musi stworzyć takie obejście.
Arie Agung
24

Link do próbek Google Po prostu skopiuj NavigationExtensions do swojej aplikacji i skonfiguruj według przykładu. Działa świetnie.

Zachar Rodionow
źródło
1
Ale działa tylko w przypadku nawigacji dolnej. Jeśli masz ogólną nawigację, masz problem
Georgiy Chebotarev
Mam ten sam problem i każde rozwiązanie, które sprawdzam, zawiera kod kotlin, ale obawiam się, że w moim projekcie używam Javy. Czy ktoś może mi pomóc, jak rozwiązać ten problem w java.
Innowacje CodeRED
@CodeREDInnovations ja też, chociaż próbowałem i pomyślnie skompilowałem z plikiem kotlin, nawigacja pozostaje taka: imgur.com/a/DcsKqPr nie "zastępuje", poza tym wydaje się, że aplikacja stała się cięższa i zacina się.
Acauã Pitta
@ AcauãPitta Czy znalazłeś rozwiązanie w Javie?
developKinberg
@CodeREDInnovations Możemy używać funkcji rozszerzających z kotlin w pliku java. Aby to zrobić, musisz skonfigurować kotlin w gradle, wygląda na to, że jest to najbardziej optymalny sposób.
Zachar Rodionow
11

Po wielu godzinach poszukiwań znalazłem rozwiązanie. Cały czas było tuż przed nami :) Jest funkcja: popBackStack(destination, inclusive)która nawiguje do podanego celu, jeśli znajdzie się w backStack. Zwraca wartość logiczną, więc możemy nawigować tam ręcznie, jeśli kontroler nie znajdzie fragmentu.

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)
    }
Piotr Prus
źródło
2
Jak i gdzie tego używać? Jeśli przejdę z fragmentu A do B, a następnie z B do A, jak mogę przejść bez wywoływania metod oncraeteView () i onViewCreated (), w skrócie bez ponownego uruchamiania fragmentu A
Kishan Solanki
@UsamaSaeedUS: Czy możesz udostępnić kod, jak go używać? Jak to, o czym wspomniał Kishan.
Code_Life
2

Jeśli masz problemy z przekazywaniem argumentów, dodaj:

fragment.arguments = args

w klasie KeepStateNavigator

Victorlopesjg
źródło
1

Obecnie niedostępne.

Aby obejść ten problem, możesz przechowywać wszystkie pobrane dane w modelu widoku i mieć te dane łatwo dostępne podczas ponownego tworzenia fragmentu. Upewnij się, że otrzymujesz widok, używając kontekstu działań.

Możesz użyć LiveData, aby obserwować cykl życia danych

Samuel Robert
źródło
2
To prawda, a data> view, ale problem występuje, gdy chcesz również zachować stan widoku, np. Załadowano wiele stron, a użytkownik przewinął w dół :).
Joaquim Ley
1

Skorzystałem z linku dostarczonego przez @STAR_ZERO i działa dobrze. Dla tych, którzy mają problem z przyciskiem Wstecz, możesz sobie z tym poradzić w hoście aktywności / nawigacji w ten sposób.

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

Po prostu sprawdź, czy aktualnym miejscem docelowym jest Twój fragment główny / domowy (zwykle pierwszy w dolnym widoku nawigacji), jeśli nie, po prostu przejdź z powrotem do fragmentu, jeśli tak, wyjdź z aplikacji lub zrób, co chcesz.

Przy okazji, to rozwiązanie musi współpracować z powyższym linkiem do rozwiązania dostarczonym przez STAR_ZERO, używając keep_state_fragment .

veeyikpong
źródło
1
idk why but currentDestination !!. id zawsze podaje mi te same wartości dla wszystkich 3 fragmentów
Avishek Das
1

Rozwiązanie dostarczone przez @ piotr-prus pomogło mi, ale musiałem dodać trochę aktualnego sprawdzenia miejsca docelowego:

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

bez tego sprawdzenia aktualny cel zostanie odtworzony, jeśli przez pomyłkę do niego przejdziesz, ponieważ nie zostałby znaleziony na tylnym stosie.

akhris
źródło
Cieszę się, że moje rozwiązanie zadziałało. Właściwie sprawdzam bieżące menuItem w bottomNavigationView przed wywołaniem fragmentu z backStack za pomocą: bottomNavigationView.setOnNavigationItemSelectedListener
Piotr Prus
-1

Zgodnie z Androidem Dokumentacji ,

Podczas tworzenia NavHostFragment przy użyciu FragmentContainerView lub w przypadku ręcznego dodawania NavHostFragment do działania za pośrednictwem FragmentTransaction, próba pobrania NavController w onCreate () działania za pośrednictwem Navigation.findNavController (Activity, @IdRes int) nie powiedzie się. Zamiast tego należy pobrać NavController bezpośrednio z NavHostFragment.

Zmień nav_host_fragment na -> FragmentContainerView i pobierz navController

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

Korzystając z tego podejścia, można zachować stan fragmentu za pomocą składników nawigacji

Gihan Sandaru
źródło