Como limpar a pilha de navegação depois de navegar para outro fragmento no Android

122

Estou usando o novo componente de arquitetura de navegação no Android e estou preso em limpar a pilha de navegação depois de mover para um novo fragmento.

Exemplo: estou no loginFragment e quero que este fragmento seja apagado da pilha quando navego para o fragmento inicial, para que o usuário não seja retornado ao loginFragment quando pressionar o botão Voltar.

Estou usando um NavHostFragment.findNavController (Fragment) .navigate (R.id.homeFragment) simples para navegar.

Código Atual:

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());
                    }
                }
            });

Tentei usar o NavOptions no navegador () , mas o botão Voltar ainda está me enviando de volta para o loginFragment

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
fonte
Você pode usar popBackStackou não adicionar LoginFragmenta backstack fornecer nullpara addToBackStack(null);e substituí-lo com o novoFragment
Yupi
Edite sua postagem e adicione o código que você está usando agora para navegar
Barns
Editei
Acho que @Yupi deu uma boa sugestão. Ou você pode usar o navigate()método como navigate(int resId, Bundle args, NavOptions navOptions)e fornecer o NavOptionsque melhor se ajusta ao seu senário
Barns
Tentei usar o NavOptions, mas o botão Voltar ainda está me enviando de volta para o loginFragment
Youssef El Behi

Respostas:

180

Primeiro, adicione atributos app:popUpTo='your_nav_graph_id'e app:popUpToInclusive="true"à tag de ação.

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

Em segundo lugar, navegue até o destino, usando a ação acima como parâmetro.

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

Veja a documentação para mais informações.

NOTA : Se você navegar usando o método navigate(@IdRes int resId), não obterá o resultado desejado. Portanto, usei o método navigate(@NonNull NavDirections directions).

Subhojit Shaw
fonte
1
Melhor resposta, já que clearTask agora está obsoleto em novas versões do Android Navigation Component!
Michał Ziobro
14
Também se pode usar o id da ação assimfindNavController(fragment).navigate(R.id.action_signInFragment_to_usersFragment)
Calin
1
Impressionante! No entanto, agora tenho o glifo backup / backup, mas pressioná-lo não adianta nada. Qual é uma maneira limpa de remover isso também?
Danish Khan
6
Acho que a chave para essa resposta está em que o "popUpTo" (que remove todos os fragmentos entre seu fragmento atual e a primeira ocorrência do id que você passa para este argumento) é definido como o id do próprio gráfico de navegação . Limpando completamente a pilha de retorno. Só adicionando este comentário porque não vi explicado exatamente assim, e era o que estava procurando. +1!
Scott O'Toole
7
NOTA : Não que a classe "Direções" não seja gerada se você não incluiu o gradle de safe-args. Para mais informações, visite aqui
Jaydip Kalkani
28

Acho que sua pergunta se refere especificamente a como usar Pop Behavior / Pop To / app: popUpTo (em xml)

Na documentação,
selecione um determinado destino antes de navegar. Isso exibe todos os destinos não correspondentes da pilha posterior até que esse destino seja encontrado.

Exemplo (aplicativo de caça de empregos simples)
meu gráfico start_screen_nav é assim:

startScreenFragment (start) -> loginFragment -> EmployerMainFragment

                            -> loginFragment -> JobSeekerMainFragment

se eu quiser navegar EmployerMainFragmente abrir todos, incluindo startScreenFragment , o código será:

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

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

se eu quiser navegar EmployerMainFragmente abrir todas as excluindo startScreenFragment , o código será:

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

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

se eu quiser navegar EmployerMainFragmente abrir, loginFragmentmas não startScreenFragment , o código será:

        <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
fonte
Como fazer isso programaticamente em vez de XML?
IgorGanapolsky
28

No meu caso, eu precisei remover tudo na pilha traseira antes de abrir um novo fragmento, então usei este código

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

a primeira linha remove a pilha traseira até atingir o fragmento especificado, no meu caso, é o fragmento inicial, portanto, remove toda a pilha traseira completamente e, quando o usuário clica de volta em fragment_company, ele fecha o aplicativo.

lamba
fonte
1
O que é fragment_apps??
IgorGanapolsky
1
O fragmento atual. É como se você estivesse no fragmentOne e quisesse o fragmentTwo, você também precisaria usar navController.popBackStack (R.id.fragmentOne, true); navController.navigate (R.id.fragmentTwo);
Bbeni
14

NOTA: Limpar tarefa está obsoleta, a descrição oficial é

Este método está obsoleto. Use setPopUpTo (int, boolean) com o id do gráfico do NavController e defina inclusive como verdadeiro.

Resposta Antiga

Se você não quer passar por tudo isso fuzz no código, você pode simplesmente verificar Clear Taskem Launch Optionsem propriedades da ação.

Opções de lançamento

Editar: a partir do Android Studio 3.2 Beta 5, Clear Task não está mais visível na janela Launch Options, mas você ainda pode usá-lo no código XML de navegação, na tag de ação , adicionando

app:clearTask="true"
Mel
fonte
Você deve usar o id da raiz do gráfico, como a mensagem de remoção de atributo do clearTask xml diz: "defina popUpTo para a raiz do seu gráfico e use popUpToInclusive". Fiz assim e alcancei o mesmo comportamento desejado com findNavController (). Navigate (@IdRes resId, bundle)
artnest
Usar a abordagem recomendada de configuração popUpTo e popUpToInclusive não parece funcionar para ações entre destinos que não sejam o destino inicial: issuetracker.google.com/issues/116831650
hsson
Na verdade, estou encontrando um problema em que não funciona com o destino inicial, o que é frustrante.
Mel
Como você faria isso se não conhecesse o fragmento no qual deseja estourar? Por exemplo, tenho um fragmento que cria um novo item. Depois de criar, quero mostrar a visão de detalhes deste item. Mas eu não quero que o usuário volte para criar o fragmento depois, mas para a visualização anterior. Pode ser uma lista de todos os itens ou um item diferente no qual o novo foi baseado. Então, clearTask é a única opção?
findusl
Não, na verdade não deve ser mais usado. Em vez disso, você deve usar popUpTo inclusivamente. Vou atualizar a resposta em breve com minhas descobertas sobre o uso do popUpTo.
Mel
5

Eu finalmente descobri isso graças a Como desativar o UP na navegação para algum fragmento com o novo componente de arquitetura de navegação?

Tive que especificar .setClearTask (true) como um 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
fonte
2
Fico feliz em ver que você conseguiu implementar minha sugestão de usar navigate(int resId, Bundle args, NavOptions navOptions)eNavOptions
Barns
Sim. Obrigado @Barns
Youssef El Behi
14
sim, "setClearTask" está obsoleto, mas em vez disso você pode usar: 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 deve funcionar. Provavelmente você está usando um fragmentId de destino errado (por exemplo, R.id.my_next_fragpode ser como login ou tela inicial); Também se você quiser popBack apenas um, você pode usar: NavHostFragment.findNavController(this).popBackStack().
Pablo Reyes
2
@PabloReyes Esta é a resposta correta. Apenas especifique qual fragmento é o último a remover: Digamos que você tenha esta pilha: Fragment_1 Fragment_2 Fragment_3 Você está agora em Fragment_3 e deseja navegar até Fragment_4 e limpar todos os outros fragmentos. Basta especificar no xml de navegação: app: popUpTo = "@ id / Fragment_1" app: popUpToInclusive = "true"
user3193413
5

Aqui está como estou fazendo isso.

 //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
fonte
Por que sua bandeira é inclusivafalse ?
IgorGanapolsky
2
De acordo com a documentação inclusive boolean: true to also pop the given destination from the back stack Então, eu defino o inclusivo como falso porque não quero retirar o fragmento atual da pilha.
Abdul Rahman Shamair
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
fonte
1

Você pode substituir o pressionamento de volta da atividade de base desta forma:

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

Nota: Isso pode não se relacionar com a questão real.

Essa abordagem será útil se uma única instância de um fragmento for necessária para manter na Navegação .

// 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
                            })

Aqui, navOptionsestá DSL e precisa ser importado. No momento desta resposta, estou usando as seguintes versões do Gradle da IU de navegação .

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

Lutei por um tempo para evitar que o botão Voltar retornasse ao meu fragmento de início, que no meu caso era uma mensagem de introdução que deveria aparecer apenas uma vez.

A solução fácil foi criar uma ação global apontando para o destino em que o usuário deveria permanecer. Você tem que definir app:popUpTo="..."corretamente - defina-o para o destino que você deseja que seja retirado. No meu caso, foi minha mensagem de introdução. Também definidoapp:popUpToInclusive="true"

Tyler
fonte
0

Posso explicar isso por meio de um pequeno exemplo. Eu tenho dois fragmentos de lista de funcionários e excluo funcionários. Em excluir funcionário, tenho dois eventos de clique para cancelar e excluir. Se eu clicar em cancelar, simplesmente volto para a tela da lista de funcionários e, se eu clicar em excluir, quero limpar todas as mochilas anteriores e ir para a tela da lista de funcionários.

Para ir para a tela anterior:

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


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

Para limpar todo o backstack anterior e ir para a nova tela:

<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())
        }

Para obter mais referências: Exemplo de navegação Jetpack

Dhrumil Shah
fonte
0

Você pode fazer tão simples quanto:

getFragmentManager().popBackStack();

Se quiser verificar a contagem, faça o seguinte:

getFragmentManager().getBackStackEntryCount()
Sr. T
fonte