IllegalArgumentException: o destino de navegação xxx é desconhecido para este NavController

138

Estou tendo problemas com o novo componente da arquitetura de navegação do Android quando tento navegar de um fragmento para outro , recebo este erro estranho:

java.lang.IllegalArgumentException: navigation destination XXX
is unknown to this NavController

Qualquer outra navegação funciona bem, exceto esta em particular.

Eu uso a findNavController()função de fragmento para obter acesso ao NavController.

Qualquer ajuda será apreciada.

Jerry Okafor
fonte
Forneça algum código para melhor compreensão.
27418 Alex
12
Isso está acontecendo comigo também.
Eury Pérez Beltré
Até agora, a taxa de ocorrência desse bug foi reduzida com os lançamentos mais recentes da biblioteca, mas acho que a biblioteca ainda não está bem documentada.
precisa saber é o seguinte

Respostas:

75

No meu caso, se o usuário clicar na mesma exibição duas vezes muito rapidamente, essa falha ocorrerá. Portanto, você precisa implementar algum tipo de lógica para evitar vários cliques rápidos ... O que é muito irritante, mas parece ser necessário.

Você pode ler mais sobre como evitar isso aqui: Android Impedindo o clique duplo em um botão

Editar 19/03/2019 : Apenas para esclarecer um pouco mais, esta falha não é reproduzível exclusivamente apenas "clicando na mesma visualização duas vezes muito rapidamente". Como alternativa, você pode usar apenas dois dedos e clicar em duas (ou mais) vistas ao mesmo tempo, onde cada uma tem sua própria navegação que executaria. Isso é especialmente fácil de fazer quando você tem uma lista de itens. As informações acima sobre prevenção de múltiplos cliques tratam desse caso.

Edit 16/4/2020 : Caso você não esteja muito interessado em ler a publicação Stack Overflow acima, estou incluindo minha própria solução (Kotlin) que uso há muito tempo.

OnSingleClickListener.kt

class OnSingleClickListener : View.OnClickListener {

    private val onClickListener: View.OnClickListener

    constructor(listener: View.OnClickListener) {
        onClickListener = listener
    }

    constructor(listener: (View) -> Unit) {
        onClickListener = View.OnClickListener { listener.invoke(it) }
    }

    override fun onClick(v: View) {
        val currentTimeMillis = System.currentTimeMillis()

        if (currentTimeMillis >= previousClickTimeMillis + DELAY_MILLIS) {
            previousClickTimeMillis = currentTimeMillis
            onClickListener.onClick(v)
        }
    }

    companion object {
        // Tweak this value as you see fit. In my personal testing this
        // seems to be good, but you may want to try on some different
        // devices and make sure you can't produce any crashes.
        private const val DELAY_MILLIS = 200L

        private var previousClickTimeMillis = 0L
    }

}

ViewExt.kt

fun View.setOnSingleClickListener(l: View.OnClickListener) {
    setOnClickListener(OnSingleClickListener(l))
}

fun View.setOnSingleClickListener(l: (View) -> Unit) {
    setOnClickListener(OnSingleClickListener(l))
}

HomeFragment.kt

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    settingsButton.setOnSingleClickListener {
        // navigation call here
    }
}
Charles Madere
fonte
23
A edição sobre o uso de 2 dedos e o clique em 2 visualizações ao mesmo tempo! Essa é a chave para mim e me ajudou a replicar o problema facilmente. Ótima atualização com essas informações.
Richard Le Mesurier
Durante a fase de depuração, cliquei enquanto o aplicativo estava parado aguardando a continuidade da execução. Parece mais um caso de dois cliques subseqüentes em uma fila para o IDE
Marco
1
Obrigado por isso. Me salvou algumas falhas e alguns coçar a cabeça :)
user2672052 27/04
58

Verifique currentDestinationantes de ligar para navegar pode ser útil.

Por exemplo, se você tiver dois destinos de fragmento no gráfico de navegação fragmentAe fragmentB, e houver apenas uma ação de fragmentApara fragmentB. a chamada navigate(R.id.action_fragmentA_to_fragmentB)resultará em IllegalArgumentExceptionquando você já estava ligado fragmentB. Portanto, você deve sempre verificar o currentDestinationantes de navegar.

if (navController.currentDestination?.id == R.id.fragmentA) {
    navController.navigate(R.id.action_fragmentA_to_fragmentB)
}
theJian
fonte
3
Eu tenho um aplicativo de pesquisa que navega com uma ação com argumentos. Assim, ele poderia navegar do currentDestination para si mesmo. Acabei fazendo o mesmo, exceto navController.currentDestination == navController.graph.node. Parecia meio sujo e eu sinto que não deveria ter que fazer isso.
Shawn Maybush
84
A biblioteca não deve nos forçar a fazer essa verificação, é realmente ridículo.
DaniloDeQueiroz
Eu tive o mesmo problema. Eu tinha um EditText e um botão 'salvar' para armazenar o conteúdo do EditText no banco de dados. Sempre travava ao pressionar o botão 'salvar'. Suspeito que o motivo esteja relacionado ao fato de que, para poder pressionar o botão 'salvar', preciso me livrar do teclado na tela tocando no botão Voltar.
The Fox
Isso verifica uma condição de erro, mas não resolve o problema. Curiosamente, essa condição é verdadeira se o backstack de navegação ficar vazio por razões indesejadas.
Mike76
1
mesmo no iOS, embora às vezes vários ViewController sejam pressionados quando você pressiona o botão várias vezes. Acho que o Android e o iOS têm esse problema.
coolcool1994 12/06
47

Você pode verificar a ação solicitada no destino atual do controlador de navegação.

ATUALIZAÇÃO adicionou o uso de ações globais para navegação segura.

fun NavController.navigateSafe(
        @IdRes resId: Int,
        args: Bundle? = null,
        navOptions: NavOptions? = null,
        navExtras: Navigator.Extras? = null
) {
    val action = currentDestination?.getAction(resId) ?: graph.getAction(resId)
    if (action != null && currentDestination?.id != action.destinationId) {
        navigate(resId, args, navOptions, navExtras)
    }
}
Alex Nuts
fonte
1
Esta solução não funcionará para nenhuma ação definida fora da currentDestinationlista de ações da. Digamos que você tenha uma ação global definida e use essa ação para navegar. Isso falhará porque a ação não está definida na lista <action> do currentDestination. Adicionar um cheque como currentDestination?.getAction(resId) != null || currentDestination?.id != resIddeve resolvê-lo, mas também pode não cobrir todos os casos.
Wchristiansen
@wchristiansen, obrigado por anotações. Atualizei o código com o uso de ações globais
Alex Nuts
@AlexNuts ótima resposta. Eu acho que você pode remover ?: graph.getAction(resId)-> currentDestination?.getAction(resId)retornará uma ação para ações globais ou não globais (eu testei). Além disso, seria melhor se você fez uso de Args seguro -> em vez passar em navDirections: NavDirectionsque resIde argsseparadamente.
Wess
@AlexNuts Observe que esta solução não suporta a navegação para o mesmo destino que o destino atual. Não é possível navegar do destino X com o Pacote Y para o destino X com o Pacote Z.
Wess
18

Também pode acontecer se você tiver um fragmento A com um ViewPager dos fragmentos B e tentar navegar de B para C

Como no ViewPager os fragmentos não são um destino de A, seu gráfico não saberia que você está em B.

Uma solução pode ser usar ADirections em B para navegar até C

AntPachon
fonte
Nesse caso, a falha não acontece sempre, mas apenas rara. Como resolver isso?
Srikar Reddy 14/05/19
Você pode adicionar uma ação global dentro do navGraph e usá-lo para navegar
Abraham Mathew
1
Como B não precisa estar ciente de seu pai exata, seria melhor para ADirections uso através de uma interface como (parentFragment as? XActionListener)?.Xaction()e observe que você poderia realizar essa função como uma variável local se isso é útil
hmac
por favor você pode compartilhar um código de exemplo para ilustrar isso como eu tenho mesmo problema
Ikhiloya Imokhai
alguém poderia plz um código de exemplo, estou preso no mesmo problema. Tenha um fragmento e depois um tabfragment
Usman Zafer
13

O que fiz para impedir a falha é o seguinte:

Eu tenho um BaseFragment, lá eu adicionei isso funpara garantir que o destinationé conhecido pelo currentDestination:

fun navigate(destination: NavDirections) = with(findNavController()) {
    currentDestination?.getAction(destination.actionId)
        ?.let { navigate(destination) }
}

Vale a pena notar que estou usando o plugin SafeArgs .

Douglas Kazumi
fonte
12

No meu caso, eu estava usando um botão voltar personalizado para navegar para cima. Liguei onBackPressed()no lugar do código a seguir

findNavController(R.id.navigation_host_fragment).navigateUp()

Isso causou a IllegalArgumentExceptionocorrência. Depois que eu mudei para usar o navigateUp()método em vez disso, não tive mais uma falha.

Neil
fonte
Eu não entendo qual é a diferença entre onBackPressed e este, ainda preso com o botão do sistema para trás e substituindo-lo e substituindo com este parece loucura
Daniel Wilson
2
Eu concordo que parece loucura. Muitas das coisas que encontrei no componente de arquitetura de navegação do Android parecem um pouco loucas, estão configuradas de maneira muito rígida para IMO. Pensando em fazer minha própria implementação para o nosso projeto, como é apenas a criação de muitas dores de cabeça
Neil
Não funciona para mim ... Ainda está recebendo o mesmo erro.
Otziii # 7/19
5

TL; DR Encerre suas navigatechamadas com try-catch(maneira simples) ou verifique se haverá apenas uma chamada navigateem um curto período de tempo. Esse problema provavelmente não desaparecerá. Copie um snippet de código maior no seu aplicativo e experimente.

Olá. Com base em algumas respostas úteis acima, gostaria de compartilhar minha solução que pode ser estendida.

Aqui está o código que causou essa falha no meu aplicativo:

@Override
public void onListItemClicked(ListItem item) {
    Bundle bundle = new Bundle();
    bundle.putParcelable(SomeFragment.LIST_KEY, item);
    Navigation.findNavController(recyclerView).navigate(R.id.action_listFragment_to_listItemInfoFragment, bundle);
}

Uma maneira de reproduzir facilmente o bug é tocar com vários dedos na lista de itens em que o clique em cada item é resolvido na navegação para a nova tela (basicamente o mesmo que as pessoas notaram - dois ou mais cliques em um período muito curto de tempo ) Eu percebi isso:

  1. A primeira navigateinvocação sempre funciona bem;
  2. A segunda e todas as outras invocações do navigatemétodo são resolvidas IllegalArgumentException.

Do meu ponto de vista, essa situação pode aparecer com muita frequência. Como a repetição de código é uma prática ruim e sempre é bom ter um ponto de influência, pensei na próxima solução:

public class NavigationHandler {

public static void navigate(View view, @IdRes int destination) {
    navigate(view, destination, /* args */null);
}

/**
 * Performs a navigation to given destination using {@link androidx.navigation.NavController}
 * found via {@param view}. Catches {@link IllegalArgumentException} that may occur due to
 * multiple invocations of {@link androidx.navigation.NavController#navigate} in short period of time.
 * The navigation must work as intended.
 *
 * @param view        the view to search from
 * @param destination destination id
 * @param args        arguments to pass to the destination
 */
public static void navigate(View view, @IdRes int destination, @Nullable Bundle args) {
    try {
        Navigation.findNavController(view).navigate(destination, args);
    } catch (IllegalArgumentException e) {
        Log.e(NavigationHandler.class.getSimpleName(), "Multiple navigation attempts handled.");
    }
}

}

E, portanto, o código acima muda apenas em uma linha a partir disso:

Navigation.findNavController(recyclerView).navigate(R.id.action_listFragment_to_listItemInfoFragment, bundle);

para isso:

NavigationHandler.navigate(recyclerView, R.id.action_listFragment_to_listItemInfoFragment, bundle);

Até ficou um pouco mais curto. O código foi testado no local exato em que a falha ocorreu. Não o experimentou mais e usará a mesma solução para outras navegações para evitar ainda mais o mesmo erro.

Quaisquer pensamentos são bem-vindos!

O que exatamente causa o acidente

Lembre-se que aqui trabalhamos com o mesmo gráfico de navegação, controlador de navegação e volta-stack quando usamos método Navigation.findNavController.

Sempre temos o mesmo controlador e gráfico aqui. Quando navigate(R.id.my_next_destination)é chamado gráfico e a pilha traseira muda quase instantaneamente enquanto a interface do usuário ainda não está atualizada. Apenas não é rápido o suficiente, mas tudo bem. Após a troca da pilha traseira, o sistema de navegação recebe a segunda navigate(R.id.my_next_destination)chamada. Como a pilha traseira mudou, agora operamos em relação ao fragmento superior da pilha. O fragmento superior é o fragmento para o qual você navega usando R.id.my_next_destination, mas não contém outros destinos com ID R.id.my_next_destination. Assim, você obtém IllegalArgumentExceptiono ID que o fragmento não conhece.

Este erro exata pode ser encontrada no NavController.javamétodo findDestination.

Jenea Vranceanu
fonte
4

No meu caso, o problema ocorreu quando eu reutilizei um dos meus fragmentos dentro de um viewpagerfragmento como filho do viewpager. O viewpagerfragmento (que era o fragmento pai) foi adicionado no xml Navigation, mas a ação não foi adicionada no viewpagerfragmento pai.

nav.xml
//reused fragment
<fragment
    android:id="@+id/navigation_to"
    android:name="com.package.to_Fragment"
    android:label="To Frag"
    tools:layout="@layout/fragment_to" >
    //issue got fixed when i added this action to the viewpager parent also
    <action android:id="@+id/action_to_to_viewall"
        app:destination="@+id/toViewAll"/>
</fragment>
....
// viewpager parent fragment
<fragment
    android:id="@+id/toViewAll"
    android:name="com.package.ViewAllFragment"
    android:label="to_viewall_fragment"
    tools:layout="@layout/fragment_view_all">

Corrigido o problema adicionando a ação ao fragmento do viewpager pai também como mostrado abaixo:

nav.xml
//reused fragment
<fragment
    android:id="@+id/navigation_to"
    android:name="com.package.to_Fragment"
    android:label="To Frag"
    tools:layout="@layout/fragment_to" >
    //issue got fixed when i added this action to the viewpager parent also
    <action android:id="@+id/action_to_to_viewall"
        app:destination="@+id/toViewAll"/>
</fragment>
....
// viewpager parent fragment
<fragment
    android:id="@+id/toViewAll"
    android:name="com.package.ViewAllFragment"
    android:label="to_viewall_fragment"
    tools:layout="@layout/fragment_view_all"/>
    <action android:id="@+id/action_to_to_viewall"
        app:destination="@+id/toViewAll"/>
</fragment>
hushed_voice
fonte
4

Hoje

def navigationVersion = "2.2.1"

O problema ainda existe. Minha abordagem sobre Kotlin é:

// To avoid "java.lang.IllegalArgumentException: navigation destination is unknown to this NavController", se more https://stackoverflow.com/q/51060762/6352712
fun NavController.navigateSafe(
    @IdRes destinationId: Int,
    navDirection: NavDirections,
    callBeforeNavigate: () -> Unit
) {
    if (currentDestination?.id == destinationId) {
        callBeforeNavigate()
        navigate(navDirection)
    }
}

fun NavController.navigateSafe(@IdRes destinationId: Int, navDirection: NavDirections) {
    if (currentDestination?.id == destinationId) {
        navigate(navDirection)
    }
}
Serg Burlaka
fonte
4

Você pode verificar antes de navegar se o fragmento que solicita a navegação ainda é o destino atual, retirado dessa essência .

Basicamente, define uma tag no fragmento para pesquisa posterior.

/**
 * Returns true if the navigation controller is still pointing at 'this' fragment, or false if it already navigated away.
 */
fun Fragment.mayNavigate(): Boolean {

    val navController = findNavController()
    val destinationIdInNavController = navController.currentDestination?.id
    val destinationIdOfThisFragment = view?.getTag(R.id.tag_navigation_destination_id) ?: destinationIdInNavController

    // check that the navigation graph is still in 'this' fragment, if not then the app already navigated:
    if (destinationIdInNavController == destinationIdOfThisFragment) {
        view?.setTag(R.id.tag_navigation_destination_id, destinationIdOfThisFragment)
        return true
    } else {
        Log.d("FragmentExtensions", "May not navigate: current destination is not the current fragment.")
        return false
    }
}

R.id.tag_navigation_destination_id é apenas um ID que você precisará adicionar ao seu ids.xml, para garantir que ele seja único. <item name="tag_navigation_destination_id" type="id" />

Mais informações sobre o bug e a solução e os navigateSafe(...)métodos de extensão em "Corrigindo o temido"… é desconhecido para este NavController ”

Frank
fonte
Estudei algumas soluções diferentes para esse problema, e a sua é definitivamente a melhor. Me deixa triste ver tão pouco amor por ele
Luke
1
pode ser útil criar um identificador exclusivo no lugar de NAV_DESTINATION_IDalgo como este stackoverflow.com/a/15021758/1572848
William Reed
sim, eu atualizei a resposta
Frank
De onde vem a tag e por que é necessária? Tenho problemas em que os IDs reais no componente de navegação não correspondem a esses R.id.
riezebosch 25/06
R.id.tag_navigation_destination_idé apenas um ID que você precisará adicionar ao seu ids.xml, para garantir que ele seja único. <item name="tag_navigation_destination_id" type="id" />
Frank
3

No meu caso, eu tinha vários arquivos de gráfico de navegação e estava tentando passar de 1 local do gráfico de navegação para um destino em outro gráfico de navegação.

Para isso, temos que incluir o segundo gráfico de navegação no primeiro como este

<include app:graph="@navigation/included_graph" />

e adicione isso à sua ação:

<action
        android:id="@+id/action_fragment_to_second_graph"
        app:destination="@id/second_graph" />

onde second_graphé:

<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/second_graph"
    app:startDestination="@id/includedStart">

no segundo gráfico.

Mais informações aqui

hushed_voice
fonte
2

No meu caso, o bug ocorreu porque eu tive uma ação de navegação com Single Topas Clear Taskopções e ativadas após uma tela inicial.

Eury Pérez Beltré
fonte
1
Mas o clearTask está obsoleto, você deve usar popUpTo ().
precisa saber é o seguinte
@ Po10cio Nenhuma dessas bandeiras foi necessária, eu a removi e ela foi consertada.
Eury Pérez Beltré
2

Eu recebi esse mesmo erro porque usei uma gaveta de navegação e getSupportFragmentManager().beginTransaction().replace( ), ao mesmo tempo, em algum lugar do meu código.

Eu me livrei do erro usando esta condição (testando se o destino):

if (Navigation.findNavController(v).getCurrentDestination().getId() == R.id.your_destination_fragment_id)
Navigation.findNavController(v).navigate(R.id.your_action);

No meu caso, o erro anterior foi acionado quando eu estava clicando nas opções da gaveta de navegação. Basicamente, o código acima ocultou o erro, porque em meu código em algum lugar eu usei a navegação usando getSupportFragmentManager().beginTransaction().replace( )A condição -

 if (Navigation.findNavController(v).getCurrentDestination().getId() ==
  R.id.your_destination_fragment_id) 

nunca foi alcançado porque (Navigation.findNavController(v).getCurrentDestination().getId()estava sempre poitando para o fragmento de casa. Você deve usar Navigation.findNavController(v).navigate(R.id.your_action)ou navegar apenas nas funções do controlador gráfico para todas as suas ações de navegação.

Aness
fonte
1

Eu peguei essa exceção depois de algumas renomeações de classes. Por exemplo: eu tive aulas chamadas FragmentAcom @+is/fragment_ano gráfico de navegação e FragmentBcom @+id/fragment_b. Então eu apaguei FragmentAe renomei FragmentBpara FragmentA. Então, depois que o nó de FragmentAainda ficou no gráfico de navegação e android:namede FragmentBnó 's foi renomeado path.to.FragmentA. Eu tinha dois nós com o mesmo android:namee diferentes android:id, e a ação necessária foi definida no nó da classe removida.

VasyaFromRussia
fonte
1

Ocorre-me quando pressiono o botão voltar duas vezes. No começo, intercepto KeyListenere anulo KeyEvent.KEYCODE_BACK. Adicionei o código abaixo na função nomeada OnResumepara o fragmento e, em seguida, esta questão / problema foi resolvido.

  override fun onResume() {
        super.onResume()
        view?.isFocusableInTouchMode = true
        view?.requestFocus()
        view?.setOnKeyListener { v, keyCode, event ->
            if (event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_BACK) {
                activity!!.finish()
                true
            }
            false
        }
    }

Quando acontece comigo pela segunda vez, e seu status é o mesmo que o primeiro, acho que talvez eu use a adsurdfunção Vamos analisar essas situações.

  1. Primeiramente, o FragmentA navega para o FragmentB, depois o FragmentB navega para o FragmentA e, em seguida, pressione o botão Voltar ... a falha aparece.

  2. Em segundo lugar, o FragmentA navega para o FragmentB, depois o FragmentB navega para o FragmentC, o FragmentC navega para o FragmentA e, em seguida, pressione o botão Voltar ... a falha aparece.

Então, ao pressionar o botão Voltar, o FragmentA retornará ao FragmentB ou FragmentC, causando a bagunça do login. Finalmente, acho que a função nomeada popBackStackpode ser usada para voltar em vez de navegar.

  NavHostFragment.findNavController(this@TeacherCloudResourcesFragment).
                        .popBackStack(
                            R.id.teacher_prepare_lesson_main_fragment,false
                        )

Até agora, o problema está realmente resolvido.

唐德坤
fonte
1

Parece que a mistura do controle fragmentManager do backstack e do controle Architecture Architecture do backstack também pode causar esse problema.

Por exemplo, a amostra básica original do CameraX usou a navegação de backstack fragmentManager como abaixo e parece que não interage corretamente com o Navigation:

// Handle back button press
        view.findViewById<ImageButton>(R.id.back_button).setOnClickListener {
            fragmentManager?.popBackStack()
        }

Se você registrar o 'destino atual' com esta versão antes de sair do fragmento principal (neste caso, o fragmento da câmera) e depois registrá-lo novamente quando retornar ao fragmento principal, poderá ver no id nos logs que o id Não é a mesma coisa. De uma maneira adivinhada, o Navigation o atualizou ao mover para o fragmento e o fragmntManager não o atualizou novamente ao voltar. Dos logs:

Antes : D / CameraXBasic: currentDest ?: androidx.navigation.fragment.FragmentNavigator$Destination@b713195

Depois : D / CameraXBasic: currentDest ?: androidx.navigation.fragment.FragmentNavigator$Destination@9807d8f

A versão atualizada do exemplo básico do CameraX usa o Navigation para retornar assim:

 // Handle back button press
        view.findViewById<ImageButton>(R.id.back_button).setOnClickListener {
            Navigation.findNavController(requireActivity(), R.id.fragment_container).navigateUp()
        }

Isso funciona corretamente e os logs mostram o mesmo ID quando voltamos ao fragmento principal.

Antes : D / CameraXBasic: currentDest ?: androidx.navigation.fragment.FragmentNavigator$Destination@b713195

Depois : D / CameraXBasic: currentDest ?: androidx.navigation.fragment.FragmentNavigator$Destination@b713195

Suspeito que a moral da história, pelo menos nesse momento, seja muito cuidadosa misturando Navegação com navegação fragmentManager.

Mick
fonte
Isso parece plausível, vou investigar mais. Alguém foi capaz de verificar ou fundamentar esta reivindicação?
Jerry Okafor
@JerryOkafor - Eu testei em um aplicativo em que estava trabalhando com base no CameraX Sample e o verifiquei, mas seria bom ver se alguém viu isso também. Na verdade, eu perdi uma 'navegação de volta' em um só lugar no mesmo aplicativo, então apenas a corrigi novamente recentemente.
Mick
1

Uma maneira ridícula mas muito poderosa é: Simplesmente chame isto:

view?.findNavController()?.navigateSafe(action)

Basta criar esta extensão:

fun NavController.navigateSafe(
    navDirections: NavDirections? = null
) {
    try {
        navDirections?.let {
            this.navigate(navDirections)
        }
    }
    catch (e:Exception)
    {
        e.printStackTrace()
    }
}
Amir Hossein Ghasemi
fonte
1

Pode haver muitas razões para esse problema. No meu caso, eu estava usando o modelo MVVM e estava observando um booleano para navegação quando o booleano é verdadeiro -> navegar mais não faz nada e isso estava funcionando bem, mas houve um erro aqui

Ao pressionar o botão voltar do fragmento de destino, eu estava encontrando o mesmo problema. E o problema era o objeto booleano, pois esqueci de alterar o valor booleano para false, isso criou a bagunça. Acabei de criar uma função no viewModel para alterar seu valor para false e chamou logo após o findNavController ()

Manoj Chouhan
fonte
1

Normalmente, quando isso acontece comigo, tive o problema descrito por Charles Madere: Dois eventos de navegação acionados na mesma interface do usuário, um alterando o currentDestination e o outro falhando porque o currentDestination é alterado. Isso pode acontecer se você clicar duas vezes ou clicar em duas visualizações com um ouvinte de clique chamando findNavController.navigate.

Portanto, para resolver isso, você pode usar if-checks, try-catch ou se estiver interessado, há um findSafeNavController () que faz isso para você antes de navegar. Ele também possui uma verificação de fiapos para garantir que você não se esqueça desse problema.

GitHub

Artigo detalhando a questão

Gergely Hegedus
fonte
1

Se você clicar rápido demais, isso causará nulo e falha.

Podemos usar RxBinding lib para ajudar nisso. Você pode adicionar acelerador e duração ao clique antes que isso aconteça.

 RxView.clicks(view).throttleFirst(duration, TimeUnit.MILLISECONDS)
            .subscribe(__ -> {
            });

Estes artigos sobre limitação no Android podem ajudar. Felicidades!

Joshua
fonte
1

Se você estiver usando uma visualização de reciclagem, adicione um cooldown do ouvinte de clique no seu clique e também no seu arquivo xml de reciclagem android:splitMotionEvents="false"

Louco
fonte
1
Veja as respostas abaixo das minhas
Crazy
1

Depois de pensar nos conselhos de Ian Lake neste tópico do twitter, vim com a seguinte abordagem. Tendo NavControllerWrapperdefinido como tal:

class NavControllerWrapper constructor(
  private val navController: NavController
) {

  fun navigate(
    @IdRes from: Int,
    @IdRes to: Int
  ) = navigate(
    from = from,
    to = to,
    bundle = null
  )

  fun navigate(
    @IdRes from: Int,
    @IdRes to: Int,
    bundle: Bundle?
  ) = navigate(
    from = from,
    to = to,
    bundle = bundle,
    navOptions = null,
    navigatorExtras = null
  )

  fun navigate(
    @IdRes from: Int,
    @IdRes to: Int,
    bundle: Bundle?,
    navOptions: NavOptions?,
    navigatorExtras: Navigator.Extras?
  ) {
    if (navController.currentDestination?.id == from) {
      navController.navigate(
        to,
        bundle,
        navOptions,
        navigatorExtras
      )
    }
  }

  fun navigate(
    @IdRes from: Int,
    directions: NavDirections
  ) {
    if (navController.currentDestination?.id == from) {
      navController.navigate(directions)
    }
  }

  fun navigateUp() = navController.navigateUp()

  fun popBackStack() = navController.popBackStack()
}

Em seguida, no código de navegação:

val navController = navControllerProvider.getNavController()
navController.navigate(from = R.id.main, to = R.id.action_to_detail)
azizbekian
fonte
1

Eu resolvi o mesmo problema colocando check antes de navegar em vez do código padrão para clicar instantaneamente no controle

 if (findNavController().currentDestination?.id == R.id.currentFragment) {
        findNavController().navigate(R.id.action_current_next)}
/* Here R.id.currentFragment is the id of current fragment in navigation graph */

de acordo com esta resposta

https://stackoverflow.com/a/56168225/7055259

noyan
fonte
0

Isso aconteceu comigo, meu problema era que eu estava clicando em um FAB tab item fragment. Eu estava tentando navegar de um fragmento de item da guia para another fragment.

Mas de acordo com Ian Lake em esta resposta temos que usar tablayoute viewpager, sem suporte componente de navegação . Por esse motivo, não há caminho de navegação do tablayout contendo fragmento para o fragmento do item de tabulação.

ex:

containing fragment -> tab layout fragment -> tab item fragment -> another fragment

A solução foi criar um caminho a partir do layout da guia contendo fragmento para o fragmento pretendido, ex: path: container fragment -> another fragment

Desvantagem:

  • O gráfico de navegação não representa mais o fluxo do usuário com precisão.
user158
fonte
0

No meu caso, recebi esse erro ao tentar navegar de outro segmento, em 50% dos casos. Executar o código no thread principal ajuda

requireActivity().runOnUiThread {
    findNavController().navigate(...)
}
Link182
fonte
Gostaria de ver mais votos sobre isso, parece plausível, mas não posso verificar.
Jerry Okafor
0

No meu caso, isso ocorreu quando adicionei acidentalmente um +destino em ação, e a falha só ocorreu quando fui ao mesmo fragmento várias vezes.

 <action
        android:id="@+id/action_to_profileFragment"
        app:destination="@+id/profileFragment" />

A solução é remover +do destino da ação, use apenas em @id/profileFragmentvez de@+id/profileFragment

 <action
        android:id="@+id/action_to_profileFragment"
        app:destination="@id/profileFragment" />
Manohar Reddy
fonte
0

Solução @Alex Nuts atualizada

Se não houver ação para um fragmento específico e desejar navegar para o fragmento

fun NavController.navigateSafe(
@IdRes actionId: Int, @IdRes fragmentId: Int, args: Bundle? = null,
navOptions: NavOptions? = null, navExtras: Navigator.Extras? = null) 
{
  if (actionId != 0) {
      val action = currentDestination?.getAction(actionId) ?: graph.getAction(actionId)
      if (action != null && currentDestination?.id != action.destinationId) {
          navigate(actionId, args, navOptions, navExtras)
    }
    } else if (fragmentId != 0 && fragmentId != currentDestination?.id)
        navigate(fragmentId, args, navOptions, navExtras)
}
Sumit
fonte
0

Eu escrevi estas extensões

fun Fragment.navigateAction(action: NavDirections) {
    val navController = this.findNavController()
    if (navController.currentDestination?.getAction(action.actionId) == null) {
        return
    } else {
        navController.navigate(action)
    }
}
Oleksandr Yahnenko
fonte
0

Eu criei esta função de extensão para o Fragment:

fun Fragment.safeNavigate(
    @IdRes actionId: Int,
    @Nullable args: Bundle? = null,
    @Nullable navOptions: NavOptions? = null,
    @Nullable navigatorExtras: Navigator.Extras? = null
) {
    NavHostFragment.findNavController(this).apply {
        if (currentDestination?.label == this@safeNavigate::class.java.simpleName) {
            navigate(actionId, args, navOptions, navigatorExtras)
        }
    }
}
Mehmed
fonte