Existe uma maneira de manter o fragmento ativo ao usar BottomNavigationView com o novo NavController?

101

Estou tentando usar o novo componente de navegação. Eu uso um BottomNavigationView com o navController: NavigationUI.setupWithNavController (bottomNavigation, navController)

Mas quando estou trocando fragmentos, eles são destruídos / criados a cada vez, mesmo que tenham sido usados ​​anteriormente.

Existe uma maneira de manter vivo nosso link de fragmentos principais para nosso BottomNavigationView?

IdAndro
fonte
3
Por comentário em issuetracker.google.com/issues/80029773 , parece que isso será resolvido eventualmente. Eu também ficaria curioso, no entanto, se as pessoas têm uma solução alternativa barata que não envolva abandonar a biblioteca para fazer este trabalho nesse ínterim.
Jimmy Alexander de

Respostas:

72

Experimente isso.

Navegador

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

Crie NavHostFragment personalizado.

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

Navigation.xml

Use a tag personalizada em vez da tag do 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>

layout de atividade

Use CustomNavHostFragment em vez 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>

Atualizar

Criei um projeto de amostra. ligação

Não crio NavHostFragment personalizado. Eu uso navController.navigatorProvider += navigator.

STAR_ZERO
fonte
3
Embora essa solução funcione ( fragmentssão reutilizados, não recriados), há um problema com a navegação. Cada menuitemnos bottomnavigationviewé considerado primário - assim, quando BACKé pressionado, os acabamentos aplicativo (ou vai para o componente superior). Uma solução melhor seria se comportar exatamente como o aplicativo do YouTube: (1) Toque em Início; (2) Tendência de toque; (3) Toque em Caixa de entrada; (4) Tendência de toque; (5) Toque em BACK- vai para a caixa de entrada; (6) Tap BACK- vai para a página inicial; (7) Tap BACK- o aplicativo existe. Em resumo, a BACKfuncionalidade do YouTube entre " menuitems" volta aos mais recentes, sem itens repetidos.
miguelt de
3
Como manter a funcionalidade do botão Voltar? O aplicativo está sendo concluído quando o botão Voltar é pressionado.
Francis
5
@ jL4, tive o mesmo problema, provavelmente você se esqueceu de remover app:navGraphdo NavHostFragment de sua atividade.
Paul Chernenko
7
Por que o Google não retira essa funcionalidade fora da caixa?
Arsenius
61
NavigationComponent deve se tornar uma solução, não outro problema, então o programador deve criar uma solução alternativa como esta.
Arie Agung
24

Link de amostras do Google Basta copiar NavigationExtensions para seu aplicativo e configurar por exemplo. Funciona bem.

Zakhar Rodionov
fonte
1
Mas funciona apenas para navegação inferior. Se você tem uma navegação geral, você tem um problema
Georgiy Chebotarev
Estou enfrentando o mesmo problema e todas as soluções que verifico contêm código kotlin, mas tenho medo de usar Java em meu projeto. Alguém pode me ajudar como corrigir esse problema em java.
CodeRED Innovations
@CodeREDInnovations eu também, embora eu tenha tentado e com sucesso compilado com o arquivo kotlin a navegação fica assim: imgur.com/a/DcsKqPr não "substitui", além disso, parece que o aplicativo ficou mais pesado e enfadonho.
Acauã Pitta
@ AcauãPitta você encontrou uma solução em Java?
DevelopKinberg
@CodeREDInnovations Podemos usar funções de extensão com kotlin no arquivo java. Para fazer isso, você precisa configurar o kotlin no gradle, parece que esta é a maneira mais ideal.
Zakhar Rodionov
11

Depois de muitas horas de pesquisa, encontrei a solução. Estava o tempo todo bem na nossa frente :) Existe uma função: popBackStack(destination, inclusive)navegar para um determinado destino se encontrado no backStack. Ele retorna booleano, para que possamos navegar até lá manualmente se o controlador não encontrar o 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)
    }
Piotr Prus
fonte
1
Como e onde usar isso? Se eu navegar do Fragmento A para B, então de B para A, como poderia ir sem chamar os métodos oncraeteView () e onViewCreated (), em suma, sem reiniciar o fragmento A
Kishan Solanki
@UsamaSaeedUS: Você pode compartilhar o código, como usá-lo? Como o que é mencionado por Kishan.
Code_Life
2

Se você tiver problemas para passar argumentos, adicione:

fragment.arguments = args

em aula KeepStateNavigator

Victorlopesjg
fonte
1

Não disponível a partir de agora.

Como uma solução alternativa, você pode armazenar todos os seus dados buscados no modelo de exibição e ter esses dados prontamente disponíveis ao recriar o fragmento. Certifique-se de obter a visualização usando o contexto de atividades.

Você pode usar o LiveData para tornar seu ciclo de vida de dados observável

Samuel Robert
fonte
2
Isso é verdade, e data> view, mas o problema é quando você deseja também manter o estado de exibição, por exemplo, várias páginas carregadas e o usuário rolou para baixo :).
Joaquim Ley
1

Usei o link fornecido por @STAR_ZERO e funciona bem. Para aqueles que estão tendo problemas com o botão Voltar, você pode manipulá-lo no host activity / nav desta forma.

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

Basta verificar se o destino atual é o seu fragmento raiz / home (normalmente o primeiro na visualização de navegação inferior), se não, navegue de volta para o fragmento, se sim, apenas saia do aplicativo ou faça o que quiser.

A propósito, esta solução precisa funcionar em conjunto com o link de solução acima fornecido por STAR_ZERO, usando keep_state_fragment .

veeyikpong
fonte
1
idk por que, mas currentDestination !!. id sempre me dando os mesmos valores para todos os 3 fragmentos
Avishek Das
1

A solução fornecida por @ piotr-prus me ajudou, mas eu tive que adicionar alguma verificação de destino atual:

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

sem esta verificação, o destino atual será recriado se você navegar por engano até ele, porque não seria encontrado na pilha de retorno.

Akhris
fonte
Estou feliz que minha solução funcionou para você. Na verdade, estou verificando o menuItem atual em bottomNavigationView antes de chamar o fragmento de backStack usando: bottomNavigationView.setOnNavigationItemSelectedListener
Piotr Prus
-1

De acordo com a documentação do Android ,

Ao criar o NavHostFragment usando FragmentContainerView ou se adicionar manualmente o NavHostFragment à sua atividade por meio de uma FragmentTransaction, a tentativa de recuperar o NavController em onCreate () de uma Activity via Navigation.findNavController (Activity, @IdRes int) falhará. Você deve recuperar o NavController diretamente do NavHostFragment.

Mude seu nav_host_fragment para -> FragmentContainerView e recupere o navController

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

Você pode preservar o estado do fragmento com Componentes de Navegação usando esta abordagem

Gihan Sandaru
fonte