Comment effacer la pile de navigation après avoir navigué vers un autre fragment dans Android

122

J'utilise le nouveau composant d'architecture de navigation dans Android et je suis bloqué dans l'effacement de la pile de navigation après avoir migré vers un nouveau fragment.

Exemple: je suis dans le loginFragment et je veux que ce fragment soit effacé de la pile lorsque je navigue vers le fragment d'accueil afin que l'utilisateur ne soit pas renvoyé au loginFragment lorsqu'il appuie sur le bouton Précédent.

J'utilise un simple NavHostFragment.findNavController (Fragment) .navigate (R.id.homeFragment) pour naviguer.

Code actuel:

mAuth.signInWithCredential(credential)
            .addOnCompleteListener(getActivity(), new OnCompleteListener<AuthResult>() {
                @Override
                public void onComplete(@NonNull Task<AuthResult> task) {
                    if (task.isSuccessful()) {
                        NavHostFragment.findNavController(LoginFragment.this).navigate(R.id.homeFragment);
                    } else {
                        Log.w(TAG, "signInWithCredential:failure", task.getException());
                    }
                }
            });

J'ai essayé d'utiliser les options de navigation dans le navigateur () , mais le bouton de retour me renvoie toujours à la connexionFragment

NavOptions.Builder navBuilder = new NavOptions.Builder();
NavOptions navOptions = navBuilder.setPopUpTo(R.id.homeFragment, false).build();   
             NavHostFragment.findNavController(LoginFragment.this).navigate(R.id.homeFragment, null, navOptions);
Youssef El Behi
la source
Vous pouvez utiliser popBackStackou ne pas ajouter LoginFragmentà backstack fournir nullà addToBackStack(null);la remplacer par une nouvelleFragment
poncho
Veuillez modifier votre message et ajouter le code que vous utilisez maintenant pour naviguer
Barns
J'ai édité ma question et ajouté mon code
Youssef El Behi
Je pense que @Yupi a fourni une bonne suggestion. Ou vous pouvez utiliser la navigate()méthode comme navigate(int resId, Bundle args, NavOptions navOptions)et fournir celle NavOptionsqui correspond le mieux à votre senario
Barns
J'ai essayé d'utiliser The NavOptions mais le bouton de retour me renvoie toujours à la connexionFragment
Youssef El Behi

Réponses:

180

Tout d'abord, ajoutez des attributs app:popUpTo='your_nav_graph_id'et app:popUpToInclusive="true"à la balise d'action.

<fragment
    android:id="@+id/signInFragment"
    android:name="com.glee.incog2.android.fragment.SignInFragment"
    android:label="fragment_sign_in"
    tools:layout="@layout/fragment_sign_in" >
    <action
        android:id="@+id/action_signInFragment_to_usersFragment"
        app:destination="@id/usersFragment"
        app:launchSingleTop="true"
        app:popUpTo="@+id/main_nav_graph"
        app:popUpToInclusive="true" />
</fragment>

Deuxièmement, accédez à la destination, en utilisant l'action ci-dessus comme paramètre.

findNavController(fragment).navigate(
     SignInFragmentDirections.actionSignInFragmentToUserNameFragment())

Consultez la documentation pour plus d'informations.

REMARQUE : si vous naviguez en utilisant la méthode navigate(@IdRes int resId), vous n'obtiendrez pas le résultat souhaité. Par conséquent, j'ai utilisé la méthode navigate(@NonNull NavDirections directions).

Subhojit Shaw
la source
1
La meilleure réponse car clearTask est désormais obsolète dans les nouvelles versions d'Android Navigation Component!
Michał Ziobro
14
On peut aussi utiliser l'identifiant de l'action comme çafindNavController(fragment).navigate(R.id.action_signInFragment_to_usersFragment)
Calin
1
Impressionnant! Cependant, j'ai maintenant le glyphe back / up, mais appuyer dessus ne fait rien. Quelle est une façon propre de supprimer cela aussi?
Danish Khan
6
Je pense que la clé de cette réponse réside dans le fait que le "popUpTo" (qui supprime tous les fragments entre votre fragment actuel et la première occurrence de l'id que vous passez à cet argument) est défini sur l' id du graphique de navigation lui-même . Effacer complètement la backstack. Ajouter seulement ce commentaire parce que je ne l'ai pas vu expliqué exactement comme ça, et c'était ce que je cherchais. +1!
Scott O'Toole
7
REMARQUE : ce n'est pas que la classe "Directions" ne sera pas générée si vous n'avez pas inclus le gradle safe-args. Pour plus d'informations, visitez ici
Jaydip Kalkani
28

Je pense que votre question concerne spécifiquement la façon d'utiliser le Comportement Pop / Pop To / app: popUpTo (en xml)

Dans la documentation,
Pop up vers une destination donnée avant de naviguer. Cela fait apparaître toutes les destinations non correspondantes de la pile arrière jusqu'à ce que cette destination soit trouvée.

Exemple (application de recherche d'emploi simple)
mon graphique start_screen_nav est comme ceci:

startScreenFragment (start) -> loginFragment -> EmployerMainFragment

                            -> loginFragment -> JobSeekerMainFragment

si je veux naviguer EmployerMainFragmentet afficher tout y compris, startScreenFragment le code sera:

        <action
            android:id="@+id/action_loginFragment_to_employerMainFragment"
            app:destination="@id/employerMainFragment"

            app:popUpTo="@+id/startScreenFragment"
            app:popUpToInclusive="true" />

si je veux naviguer EmployerMainFragmentet afficher tout sauf, startScreenFragment le code sera:

        <action
            android:id="@+id/action_loginFragment_to_employerMainFragment"
            app:destination="@id/employerMainFragment"

            app:popUpTo="@+id/startScreenFragment"/>

si je veux naviguer EmployerMainFragmentet faire apparaître loginFragmentmais pas, startScreenFragment le code sera:

        <action
            android:id="@+id/action_loginFragment_to_employerMainFragment"
            app:destination="@id/employerMainFragment"

            app:popUpTo="@+id/loginFragment"
            app:popUpToInclusive="true"/>

OU

        <action
            android:id="@+id/action_loginFragment_to_employerMainFragment"
            app:destination="@id/employerMainFragment"

            app:popUpTo="@+id/startScreenFragment"/>
Dongkichan
la source
Comment faire cela par programme au lieu de XML?
IgorGanapolsky le
28

Dans mon cas, j'avais besoin de tout supprimer dans la pile arrière avant d'ouvrir un nouveau fragment, j'ai donc utilisé ce code

navController.popBackStack(R.id.fragment_apps, true);
navController.navigate(R.id.fragment_company);

la première ligne supprime la pile arrière jusqu'à ce qu'elle atteigne le fragment spécifié dans mon cas, c'est le fragment d'origine, donc elle supprime complètement toute la pile arrière, et lorsque l'utilisateur clique à nouveau dans la société fragment_company, il ferme l'application.

lamba
la source
1
Qu'est-ce que c'est fragment_apps??
IgorGanapolsky le
1
Le fragment actuel. C'est comme si vous étiez sur fragmentOne et que vous vouliez aller fragmentTwo, vous devez aussi utiliser navController.popBackStack (R.id.fragmentOne, true); navController.navigate (R.id.fragmentTwo);
Bbeni le
14

REMARQUE: tâche Clear est obsolète, la description officielle est

Cette méthode est obsolète. Utilisez setPopUpTo (int, boolean) avec l'ID du graphique du NavController et définissez inclusive sur true.

Ancienne réponse

Si vous ne voulez pas passer par tout ce qui fuzz dans le code, vous pouvez simplement vérifier Clear Taskdans Launch Optionsles propriétés de l'action.

Options de lancement

Modifier: à partir d'Android Studio 3.2 Beta 5, Effacer la tâche n'est plus visible dans la fenêtre Options de lancement, mais vous pouvez toujours l'utiliser dans le code XML de la navigation, dans la balise d' action , en ajoutant

app:clearTask="true"
Mel
la source
Vous devez utiliser l'id de la racine du graphe à la place, comme le message de précision attribuée au xml clearTask dit: "réglez popUpTo à la racine de votre graphe et utilisez popUpToInclusive". Je l'ai fait comme ça et j'ai obtenu le même comportement souhaité avec findNavController (). Naviguer (@IdRes resId, bundle)
artnest
L'utilisation de l'approche recommandée pour définir popUpTo et popUpToInclusive ne semble pas fonctionner pour les actions entre des destinations autres que la destination de départ: issuetracker.google.com/issues/116831650
hsson
Je rencontre en fait un problème où cela ne fonctionne pas avec la destination de départ, ce qui est frustrant.
Mel du
Comment feriez-vous cela si vous ne connaissez pas le fragment sur lequel vous souhaitez apparaître? Par exemple, j'ai un fragment qui crée un nouvel élément. Après la création, je souhaite afficher la vue détaillée de cet élément. Mais je ne veux pas que l'utilisateur revienne ensuite au fragment de création, mais à la vue précédente. Cela peut être une liste de tous les éléments ou un élément différent sur lequel le nouveau était basé. ClearTask est-il alors la seule option?
findusl
Non, il ne devrait plus être utilisé. Au lieu de cela, vous devez utiliser popUpTo inclusivement. Je mettrai à jour la réponse très bientôt avec mes conclusions sur l'utilisation de popUpTo.
Mel
5

Je le comprends enfin grâce à Comment désactiver UP dans la navigation pour un fragment avec le nouveau composant d'architecture de navigation?

J'ai dû spécifier .setClearTask (true) comme NavOption.

mAuth.signInWithCredential(credential)
            .addOnCompleteListener(getActivity(), new OnCompleteListener<AuthResult>() {
                @Override
                public void onComplete(@NonNull Task<AuthResult> task) {
                    if (task.isSuccessful()) {
                        Log.d(TAG, "signInWithCredential:success");


                        NavOptions.Builder navBuilder = new NavOptions.Builder();
                        NavOptions navOptions = navBuilder.setClearTask(true).build();
                        NavHostFragment.findNavController(LoginFragment.this).navigate(R.id.homeFragment,null,navOptions);
                    } else {
                        Log.w(TAG, "signInWithCredential:failure", task.getException());

                    }

                }
            });
Youssef El Behi
la source
2
Heureux de voir que vous avez pu mettre en œuvre ma suggestion d'utilisation navigate(int resId, Bundle args, NavOptions navOptions)etNavOptions
Barns
Oui. Merci @Barns
Youssef El Behi
14
ouais "setClearTask" est obsolète mais à la place, vous pouvez utiliser: val navOptions = NavOptions.Builder().setPopUpTo(R.id.my_current_frag, true).build() findNavController().navigate(R.id.my_next_frag, null, navOptions)
Pablo Reyes
1
@ c-an ça devrait marcher. Vous utilisez probablement un fragmentId de destination incorrect (par exemple, cela R.id.my_next_fragpourrait ressembler à un écran de connexion ou de démarrage); aussi si vous voulez popBack un seul, vous pouvez utiliser: NavHostFragment.findNavController(this).popBackStack().
Pablo Reyes
2
@PabloReyes C'est la bonne réponse. Spécifiez simplement quel fragment est le dernier à supprimer: Supposons que vous ayez cette pile: Fragment_1 Fragment_2 Fragment_3 Vous êtes maintenant sur Fragment_3 et souhaitez accéder à Fragment_4 et effacer tous les autres fragments. Spécifiez simplement dans le xml de navigation: app: popUpTo = "@ id / Fragment_1" app: popUpToInclusive = "true"
user3193413
5

Voici comment j'y parviens.

 //here the R.id refer to the fragment one wants to pop back once pressed back from the newly  navigated fragment
 val navOption = NavOptions.Builder().setPopUpTo(R.id.startScorecardFragment, false).build()

//now how to navigate to new fragment
Navigation.findNavController(this, R.id.my_nav_host_fragment)
                    .navigate(R.id.instoredBestPractice, null, navOption)
Abdul Rahman Shamair
la source
Pourquoi votre drapeau inclusiffalse ?
IgorGanapolsky le
2
Selon la documentation inclusive boolean: true to also pop the given destination from the back stack Donc, j'ai défini l'inclusif sur false car je ne veux pas sortir le fragment actuel de la pile.
Abdul Rahman Shamair le
4
    NavController navController 
    =Navigation.findNavController(requireActivity(),          
    R.id.nav_host_fragment);// initialize navcontroller

    if (navController.getCurrentDestination().getId() == 
     R.id.my_current_frag) //for avoid crash
  {
    NavDirections action = 
    DailyInfoSelectorFragmentDirections.actionGoToDestionationFragment();

    //for clear current fragment from stack
    NavOptions options = new 
    NavOptions.Builder().setPopUpTo(R.id.my_current_frag, true).build();
    navController.navigate(action, options);
    }
emad pirayesh
la source
1

Vous pouvez remplacer la pression arrière de l'activité de base comme ceci:

override fun onBackPressed() {

  val navigationController = nav_host_fragment.findNavController()
  if (navigationController.currentDestination?.id == R.id.firstFragment) {
    finish()
  } else if (navigationController.currentDestination?.id == R.id.secondFragment) {
    // do nothing
  } else {
    super.onBackPressed()
  }

}
ROHIT LIEN
la source
1

Remarque: cela peut ne pas être lié à la question réelle.

Cette approche sera utile si une seule instance d'un fragment est nécessaire pour la maintenance dans Navigation .

// imports
import androidx.navigation.navOptions

// navigate to fragment
navController.navigate(R.id.nav_single_instance_fragment, null,
                            navOptions {
                                popUpTo = R.id.nav_single_instance_fragment
                                launchSingleTop = true
                            })

Ici, navOptionsest DSL et doit être importé. Au moment de cette réponse, j'utilise les versions gradées suivantes de l' interface utilisateur de navigation .

    def nav_version = "2.2.0"
    implementation "androidx.navigation:navigation-ui:$nav_version"
    implementation "androidx.navigation:navigation-fragment:$nav_version"
    implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
    implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
Sagar Chapagain
la source
0

J'ai lutté pendant un moment pour empêcher le bouton de retour de revenir à mon fragment de départ, qui dans mon cas était un message d'introduction qui ne devrait apparaître qu'une seule fois.

La solution simple était de créer une action globale pointant vers la destination sur laquelle l'utilisateur doit rester. Vous devez définir app:popUpTo="..."correctement - réglez-le sur la destination que vous souhaitez faire sauter. Dans mon cas, c'était mon message d'introduction. Définir égalementapp:popUpToInclusive="true"

Tyler
la source
0

Je peux expliquer cela par un petit exemple. J'ai deux fragments de liste d'employés et je supprime l'employé. Dans supprimer un employé, j'ai deux événements de clic pour annuler et supprimer. Si je clique sur Annuler, il suffit de revenir à l'écran de la liste des employés et si je clique sur Supprimer, je veux effacer toutes les piles de dos précédentes et aller à l'écran de la liste des employés.

Pour aller à l'écran précédent:

<action
        android:id="@+id/action_deleteEmployeeFragment_to_employeesListFragment2"
        app:destination="@id/employeesListFragment"/>


    btn_cancel.setOnClickListener {
                it.findNavController().popBackStack()
            }

Pour effacer tous les backstack précédents et aller au nouvel écran:

<action
        android:id="@+id/action_deleteEmployeeFragment_to_employeesListFragment2"
        app:destination="@id/employeesListFragment"
        app:popUpTo="@id/employeesListFragment"
        app:popUpToInclusive="true"
        app:launchSingleTop="true" />


 btn_submit.setOnClickListener {
                   it.findNavController().navigate(DeleteEmployeeFragmentDirections.actionDeleteEmployeeFragmentToEmployeesListFragment2())
        }

Pour plus de références: Exemple de navigation Jetpack

Dhrumil Shah
la source
0

Vous pouvez faire aussi simple que:

getFragmentManager().popBackStack();

Si vous voulez vérifier le décompte, vous pouvez faire comme:

getFragmentManager().getBackStackEntryCount()
Monsieur T
la source