Gibt es eine Möglichkeit, das Fragment am Leben zu erhalten, wenn BottomNavigationView mit dem neuen NavController verwendet wird?

88

Ich versuche, die neue Navigationskomponente zu verwenden. Ich verwende eine BottomNavigationView mit dem navController: NavigationUI.setupWithNavController (bottomNavigation, navController)

Aber wenn ich Fragmente wechsle, werden sie jedes Mal zerstört / erstellt, selbst wenn sie zuvor verwendet wurden.

Gibt es eine Möglichkeit, unseren Hauptfragment-Link zu unserer BottomNavigationView am Leben zu erhalten?

IdAndro
quelle
2
Laut Kommentar auf issuetracker.google.com/issues/80029773 scheint dies irgendwann behoben zu sein. Ich wäre jedoch auch neugierig, wenn die Leute eine billige Problemumgehung haben, bei der die Bibliothek nicht verlassen wird, damit dies in der Zwischenzeit funktioniert.
Jimmy Alexander

Antworten:

64

Versuche dies.

Navigator

Erstellen Sie einen benutzerdefinierten Navigator.

@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

Erstellen Sie ein benutzerdefiniertes NavHostFragment.

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

navigation.xml

Verwenden Sie ein benutzerdefiniertes Tag anstelle eines Fragment-Tags.

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

Aktivitätslayout

Verwenden Sie CustomNavHostFragment anstelle von 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>

Aktualisieren

Ich habe ein Beispielprojekt erstellt. Verknüpfung

Ich erstelle kein benutzerdefiniertes NavHostFragment. Ich benutze navController.navigatorProvider += navigator.

STAR_ZERO
quelle
3
Während diese Lösung funktioniert ( fragmentswiederverwendet, nicht neu erstellt), gibt es ein Problem mit der Navigation. Jedes menuitemIn bottomnavigationviewwird als primär betrachtet. Wenn Sie BACKalso drücken, wird die App beendet (oder geht zur obersten Komponente). Eine bessere Lösung wäre, sich wie die YouTube-App zu verhalten: (1) Tippen Sie auf Startseite. (2) Tippen Sie auf Trend; (3) Tippen Sie auf Posteingang. (4) Tippen Sie auf Trend; (5) Tippen BACK- geht zum Posteingang; (6) Tippen BACK- geht nach Hause; (7) Tap BACK- App existiert. Zusammenfassend lässt sich sagen, dass die YouTube- BACKFunktionalität zwischen " menuitems" auf die neueste zurückgesetzt wird, ohne dass Elemente wiederholt werden.
Miguelt
3
Wie kann ich die Funktionalität der Zurück-Taste beibehalten? Die App wird beendet, wenn die Zurück-Taste gedrückt wird.
Francis
5
@ jL4, ich hatte das gleiche Problem, wahrscheinlich haben Sie vergessen, app:navGraphdas NavHostFragment Ihrer Aktivität zu entfernen.
Paul Chernenko
6
Warum bietet Google diese Funktionalität nicht sofort an?
Arsenius
51
NavigationComponent sollte eine Lösung werden, nicht ein weiteres Problem, daher muss der Programmierer eine solche Problemumgehung erstellen.
Arie Agung
22

Google-Beispiellink Kopieren Sie einfach NavigationExtensions in Ihre Anwendung und konfigurieren Sie sie anhand eines Beispiels. Funktioniert super.

Zakhar Rodionov
quelle
1
Es funktioniert aber nur für die untere Navigation. Wenn Sie eine allgemeine Navigation haben, haben Sie ein Problem
Georgiy Chebotarev
Ich habe das gleiche Problem und jede Lösung, die ich überprüfe, enthält Kotlin-Code, aber ich befürchte, dass ich in meinem Projekt Java verwende. Kann mir jemand helfen, wie man dieses Problem in Java behebt.
CodeRED Innovations
11

Nach vielen Stunden der Recherche fand ich eine Lösung. Es war die ganze Zeit direkt vor uns :) Es gibt eine Funktion: popBackStack(destination, inclusive)die zu einem bestimmten Ziel navigiert, wenn sie in backStack gefunden wird. Es gibt Boolean zurück, sodass wir manuell dorthin navigieren können, wenn der Controller das Fragment nicht findet.

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
quelle
Wie und wo soll man das nutzen? Wenn ich von Fragment A nach B navigiere, dann von B nach A, wie könnte ich gehen, ohne die Methoden oncraeteView () und onViewCreated () aufzurufen, kurz ohne Fragment A
Kishan Solanki
Nun, es hat wie ein Zauber funktioniert, danke <3
Usama Saeed US
@UsamaSaeedUS: Können Sie bitte Code teilen, wie man ihn benutzt? Wie das, was Kishan erwähnt.
Code_Life
1

Ab sofort nicht verfügbar.

Um dieses Problem zu umgehen, können Sie alle abgerufenen Daten im Ansichtsmodell speichern und diese Daten beim erneuten Erstellen des Fragments sofort verfügbar machen. Stellen Sie sicher, dass Sie die Ansicht im Aktivitätskontext erhalten.

Sie können LiveData verwenden, um Ihre Datenlebenszyklusdaten beobachtbar zu machen

Samuel Robert
quelle
1
Dies ist in der Tat wahr und Daten> Ansicht, aber das Problem ist, wenn Sie auch den Ansichtsstatus beibehalten möchten, z. B. mehrere Seiten geladen und der Benutzer nach unten gescrollt hat :).
Joaquim Ley
Dies funktioniert in meinem Anwendungsfall gut.
Bolling
1

Ich habe den von @STAR_ZERO bereitgestellten Link verwendet und er funktioniert einwandfrei. Für diejenigen, die Probleme mit der Zurück-Schaltfläche haben, können Sie dies im Aktivitäts- / Navigations-Host wie folgt behandeln.

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

Überprüfen Sie einfach, ob das aktuelle Ziel Ihr Root- / Home-Fragment ist (normalerweise das erste in der unteren Navigationsansicht). Wenn nicht, navigieren Sie einfach zurück zum Fragment. Wenn ja, beenden Sie nur die App oder tun Sie, was Sie wollen.

Übrigens muss diese Lösung mit dem oben von STAR_ZERO bereitgestellten Lösungslink unter Verwendung von keep_state_fragment zusammenarbeiten .

veeyikpong
quelle
idk warum aber currentDestination !!. id gibt mir immer die gleichen Werte für alle 3 Fragmente
Avishek Das
1

Die von @ piotr-prus bereitgestellte Lösung hat mir geholfen, aber ich musste einige aktuelle Zielprüfungen hinzufügen:

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

Ohne diese Überprüfung wird das aktuelle Ziel neu erstellt, wenn Sie versehentlich dorthin navigieren, da es nicht im Backstack gefunden wird.

Akhris
quelle
Ich bin froh, dass meine Lösung für Sie funktioniert hat. Ich überprüfe gerade das aktuelle menuItem in bottomNavigationView, bevor ich Fragment von backStack mit: bottomNavigationView.setOnNavigationItemSelectedListener
Piotr Prus
1

Wenn Sie Probleme beim Übergeben von Argumenten haben, fügen Sie Folgendes hinzu:

fragment.arguments = args

im Unterricht KeepStateNavigator

Victorlopesjg
quelle