IllegalArgumentException: cel nawigacji xxx jest nieznany dla tego kontrolera NavController


139

Mam problem z nowym komponentem Android Navigation Architecture, gdy próbuję przejść z jednego fragmentu do drugiego , pojawia się ten dziwny błąd:

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

Każda inna nawigacja działa dobrze, z wyjątkiem tej konkretnej.

Używam findNavController()funkcji Fragment, aby uzyskać dostęp do NavController.

Każda pomoc zostanie doceniona.


Podaj kod, aby lepiej zrozumieć.
Alex

12
Mnie też się to przytrafia.
Eury Pérez Beltré

Jak dotąd częstotliwość występowania tego błędu została zmniejszona w nowszych wersjach biblioteki, ale myślę, że biblioteka nie jest jeszcze dobrze udokumentowana.
Jerry Oka na

Odpowiedzi:


76

W moim przypadku, jeśli użytkownik bardzo szybko kliknie ten sam widok dwa razy, nastąpi awaria. Musisz więc zaimplementować jakąś logikę, aby zapobiec wielokrotnym szybkim kliknięciom ... Co jest bardzo denerwujące, ale wydaje się konieczne.

Możesz przeczytać więcej na temat zapobiegania temu tutaj: Android Zapobieganie dwukrotnemu kliknięciu przycisku

Edycja 19.03.2019 : Aby wyjaśnić nieco dokładniej, tej awarii nie można odtworzyć wyłącznie poprzez „bardzo szybkie kliknięcie tego samego widoku dwa razy, bardzo szybko”. Alternatywnie możesz po prostu użyć dwóch palców i kliknąć dwa (lub więcej) widoki w tym samym czasie, gdzie każdy widok ma własną nawigację, którą by wykonywał. Jest to szczególnie łatwe, gdy masz listę przedmiotów. Powyższe informacje o zapobieganiu wielokrotnym kliknięciom pomogą w tym przypadku.

Edycja 4/16/2020 : Na wypadek, gdybyś nie był bardzo zainteresowany przeczytaniem powyższego postu Stack Overflow, dołączam moje własne rozwiązanie (Kotlin), którego używam od dłuższego czasu.

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

23
Edycja polegająca na użyciu 2 palców i jednoczesnym klikaniu 2 widoków! To jest dla mnie klucz i pomogło mi łatwo odtworzyć problem. Świetna aktualizacja z tymi informacjami.
Richard Le Mesurier

Podczas fazy debugowania zdarzyło mi się kliknąć, gdy aplikacja utknęła, czekając na kontynuację wykonywania. Wygląda na to, że dwa kolejne kliknięcia z rzędu prowadzą do IDE
Marco

1
Dzięki za to. Zaoszczędził mi kilka wypadków i trochę drapania po głowie :)
user2672052

58

Sprawdź, currentDestinationzanim zadzwonisz, nawigacja może być pomocna.

Na przykład, jeśli masz dwa miejsca docelowe fragmentów na wykresie nawigacyjnym fragmentAi fragmentBi jest tylko jedna akcja od fragmentAdo fragmentB. wywołanie navigate(R.id.action_fragmentA_to_fragmentB)spowoduje, IllegalArgumentExceptionże już byłeś na fragmentB. Dlatego zawsze powinieneś sprawdzić currentDestinationprzed rozpoczęciem nawigacji.

if (navController.currentDestination?.id == R.id.fragmentA) {
    navController.navigate(R.id.action_fragmentA_to_fragmentB)
}

3
Mam aplikację do wyszukiwania, która obsługuje akcję z argumentami. W ten sposób może nawigować od currentDestination do siebie. Skończyło się na tym, że zrobiłem to samo, z wyjątkiem navController.currentDestination == navController.graph.node. To było trochę brudne i czuję, że nie powinienem tego robić.
Shawn Maybush

86
Biblioteka nie powinna nas do tego zmuszać, to rzeczywiście śmieszne.
DaniloDeQueiroz

Miałem ten sam problem. Miałem EditText i przycisk „save” do przechowywania zawartości EditText w bazie danych. Zawsze się zawieszał po naciśnięciu przycisku „Zapisz”. Podejrzewam, że przyczyna tkwi w tym, że aby móc nacisnąć przycisk „zapisz”, muszę pozbyć się klawiatury ekranowej, dotykając przycisku Wstecz.
The Fox

To sprawdza, czy wystąpił błąd, ale nie rozwiązuje problemu. Co ciekawe, ten warunek jest prawdziwy, jeśli backstack nawigacji staje się pusty z niepożądanych powodów.
Mike76

1
nawet w iOS, chociaż czasami wiele ViewController zostaje wciśniętych po wielokrotnym naciśnięciu przycisku. Zgadnij, że zarówno Android, jak i iOS mają ten problem.
coolcool1994

47

Możesz sprawdzić żądaną akcję w aktualnym miejscu docelowym kontrolera nawigacyjnego.

UPDATE dodał użycie globalnych działań dla bezpiecznej nawigacji.

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

1
To rozwiązanie nie będzie działać dla żadnych akcji zdefiniowanych poza currentDestinationlistą akcji. Załóżmy, że masz zdefiniowaną akcję globalną i użyj jej do nawigacji. To się nie powiedzie, ponieważ akcja nie jest zdefiniowana na liście <action> currentDestination. Dodanie czeku like currentDestination?.getAction(resId) != null || currentDestination?.id != resIdpowinno rozwiązać ten problem, ale może też nie obejmować wszystkich przypadków.
wchristiansen

@wchristiansen, dziękuję za uwagi. Zaktualizowałem kod przy użyciu działań globalnych
Alex Nuts

@AlexNuts świetna odpowiedź. Myślę, że możesz usunąć ?: graph.getAction(resId)-> currentDestination?.getAction(resId)zwróci akcję zarówno dla akcji globalnych, jak i nieglobalnych (przetestowałem to). Ponadto, będzie lepiej jeśli zostały dokonane użyć bezpiecznego args -> raczej zdać się navDirections: NavDirectionsniż resIdi argsoddzielnie.
Wess

@AlexNuts Uwaga: to rozwiązanie nie obsługuje nawigacji do tego samego miejsca docelowego, co bieżące miejsce docelowe. Nawigacja z miejsca docelowego X z pakietem Y do miejsca docelowego X z pakietem Z nie jest możliwa.
Wess

18

Może się to również zdarzyć, jeśli masz Fragment A z ViewPager fragmentów B i próbujesz przejść z B do C

Ponieważ w ViewPager fragmenty nie są miejscem docelowym A, twój wykres nie wiedziałby, że jesteś na B.

Rozwiązaniem może być użycie ADirections w B, aby przejść do C


W takim przypadku awaria nie występuje za każdym razem, a zdarza się rzadko. Jak to rozwiązać?
Srikar Reddy

Możesz dodać globalną akcję w navGraph i używać jej do nawigacji
Abraham Mathew

1
Jako B nie powinno muszą zdawać sobie sprawę z jej dokładnym rodzica, byłoby lepiej użyć ADirections poprzez interfejs jak (parentFragment as? XActionListener)?.Xaction()i zanotować można trzymać tę funkcję jako zmiennej lokalnej, czy to jest pomocne
hmac

czy możesz udostępnić przykładowy kod ilustrujący to, ponieważ mam ten sam problem
Ikhiloya Imokhai,

każdy mógłby pobrać przykładowy kod, utknąłem w tym samym problemie. Zrób fragment, a następnie tabfragment
Usman Zafer

13

Oto co zrobiłem, aby zapobiec awarii:

Mam BaseFragment, tam dodałem to, funaby upewnić się, że destinationjest znany przez currentDestination:

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

Warto zauważyć, że używam wtyczki SafeArgs .


12

W moim przypadku używałem niestandardowego przycisku Wstecz do nawigacji w górę. Zadzwoniłem onBackPressed()zamiast poniższego kodu

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

To spowodowało IllegalArgumentExceptionwystąpienie. Po tym, jak zmieniłem go, aby navigateUp()zamiast tego używać metody, nie miałem ponownie awarii.


Nie rozumiem, jaka jest różnica między onBackPressed a tym, wciąż utknąłem z przyciskiem wstecz systemu i zastąpienie go i zastąpienie tym wydaje się szalone
Daniel Wilson

2
Zgadzam się, że to wydaje się szalone. Wiele rzeczy, które napotkałem w komponencie architektury nawigacji na Androida, wydaje się nieco szalonych, jest skonfigurowany zbyt sztywno IMO. Myślenie o zrobieniu własnej implementacji dla naszego projektu, ponieważ powoduje to po prostu zbyt wiele bólów głowy
Neil,

Nie działa dla mnie ... Nadal pojawia się ten sam błąd.
Otziii

6

TL; DR Owijaj swoje navigatepołączenia try-catch(w prosty sposób) lub upewnij się, że navigatew krótkim czasie będzie tylko jedno połączenie . Ten problem prawdopodobnie nie zniknie. Skopiuj większy fragment kodu do swojej aplikacji i wypróbuj.

Dzień dobry. W oparciu o kilka przydatnych odpowiedzi powyżej, chciałbym podzielić się moim rozwiązaniem, które można rozszerzyć.

Oto kod, który spowodował awarię w mojej aplikacji:

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

Sposobem na łatwe odtworzenie błędu jest dotknięcie wieloma palcami listy elementów, gdzie kliknięcie każdego elementu powoduje przejście do nowego ekranu (w zasadzie to samo, co ludzie zauważyli - dwa lub więcej kliknięć w bardzo krótkim czasie ). Zauważyłem to:

  1. Pierwsze navigatewywołanie zawsze działa dobrze;
  2. Drugie i wszystkie inne wywołania navigatemetody są rozwiązywane w IllegalArgumentException.

Z mojego punktu widzenia taka sytuacja może pojawiać się bardzo często. Ponieważ powtarzanie kodu jest złą praktyką i zawsze dobrze jest mieć jeden punkt wpływu, pomyślałem o następnym rozwiązaniu:

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.");
    }
}

}

I tak powyższy kod zmienia się tylko w jednej linii z tego:

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

do tego:

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

Stało się nawet trochę krótsze. Kod został przetestowany w dokładnym miejscu, w którym nastąpiła awaria. Już tego nie doświadczyłem i użyję tego samego rozwiązania w innych nawigacjach, aby dalej uniknąć tego samego błędu.

Wszelkie myśli są mile widziane!

Co dokładnie powoduje awarię

Pamiętaj, że tutaj pracujemy z tym samym wykresem nawigacyjnym, kontrolerem nawigacji i stosem wstecznym, gdy używamy metody Navigation.findNavController.

Tutaj zawsze otrzymujemy ten sam kontroler i wykres. Kiedy navigate(R.id.my_next_destination)nazywa się wykres i zmiany stosu wstecznego prawie natychmiast, gdy interfejs użytkownika nie jest jeszcze aktualizowany. Po prostu nie wystarczająco szybko, ale to jest w porządku. Po zmianie stosu system nawigacji odbiera drugie navigate(R.id.my_next_destination)wywołanie. Ponieważ stos się zmienił, działamy teraz względem górnego fragmentu stosu. Górny fragment to fragment, do którego nawigujesz R.id.my_next_destination, ale nie zawiera żadnych dalszych celów z identyfikatorem R.id.my_next_destination. W ten sposób otrzymujesz IllegalArgumentExceptionze względu na identyfikator, o którym fragment nic nie wie.

Dokładny błąd można znaleźć w NavController.javametodzie findDestination.


4

W moim przypadku problem wystąpił, gdy ponownie użyłem jednego z moich fragmentów wewnątrz viewpagerfragmentu jako dziecko viewpager. viewpagerFragment (który był fragment rodzic) dodano xml nawigacji, ale działanie to zostało dodane w viewpagerfragmentu macierzystego.

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

Naprawiono problem, dodając akcję do nadrzędnego fragmentu podglądu, jak pokazano poniżej:

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>

4

Dzisiaj

def navigationVersion = "2.2.1"

Problem nadal istnieje. Moje podejście do Kotlina to:

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

4

Możesz sprawdzić przed rozpoczęciem nawigacji, czy fragment żądający nawigacji jest nadal aktualnym celem, wzięty z tego sedna .

Zasadniczo ustawia znacznik na fragmencie do późniejszego wyszukiwania.

/**
 * 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 to tylko identyfikator, który musisz dodać do swojego ids.xml, aby upewnić się, że jest unikalny. <item name="tag_navigation_destination_id" type="id" />

Więcej informacji o błędzie i rozwiązaniu oraz navigateSafe(...)metodach rozszerzania w „Naprawianie przerażających”… jest nieznane temu NavController ”


Przestudiowałem kilka różnych rozwiązań tego problemu, a twoje jest zdecydowanie najładniejsze. Smutno mi, widząc tak mało miłości do tego
Łukasza

1
może być przydatne utworzenie unikalnego identyfikatora zamiast NAV_DESTINATION_IDczegoś takiego jak ten stackoverflow.com/a/15021758/1572848
William Reed

tak, zaktualizowałem odpowiedź
Frank

Skąd pochodzi tag i dlaczego jest potrzebny? Mam problemy z tym, że rzeczywisty identyfikator w komponencie nawigacji nie pasuje do tych z R.id.
riezebosch

R.id.tag_navigation_destination_idto tylko identyfikator, który musisz dodać do swojego ids.xml, aby upewnić się, że jest unikalny. <item name="tag_navigation_destination_id" type="id" />
Frank

3

W moim przypadku miałem wiele plików z wykresami nawigacyjnymi i próbowałem przejść z 1 lokalizacji wykresu nawigacyjnego do miejsca docelowego na innym wykresie nawigacyjnym.

W tym celu musimy dołączyć drugi wykres nawigacyjny do pierwszego w ten sposób

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

i dodaj to do swojej akcji:

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

gdzie second_graphjest:

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

na drugim wykresie.

Więcej informacji tutaj


3

Rozwiązałem ten sam problem, umieszczając check przed nawigacją zamiast standardowego kodu do natychmiastowego kliknięcia

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

zgodnie z tą odpowiedzią

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


2

W moim przypadku bug ocurred bo miałem działanie nawigacji z Single Topi Clear Taskopcje włączone po ekranie powitalnym.


1
Ale clearTask jest przestarzałe, powinieneś zamiast tego użyć popUpTo ().
Jerry Oka na

@ Po10cio Żadna z tych flag nie była potrzebna, właśnie ją usunąłem i zostało to naprawione.
Eury Pérez Beltré

2

Otrzymałem ten sam błąd, ponieważ użyłem szuflady nawigacji i getSupportFragmentManager().beginTransaction().replace( )jednocześnie gdzieś w moim kodzie.

Pozbyłem się błędu, korzystając z tego warunku (testowanie, czy miejsce docelowe):

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

W moim przypadku poprzedni błąd został wywołany, gdy klikałem opcje szuflady nawigacji. Zasadniczo powyższy kod ukrył błąd, ponieważ w moim kodzie gdzieś użyłem nawigacji z użyciem getSupportFragmentManager().beginTransaction().replace( )warunku -

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

nigdy nie został osiągnięty, ponieważ (Navigation.findNavController(v).getCurrentDestination().getId()zawsze zmierzał do fragmentu domu. Do Navigation.findNavController(v).navigate(R.id.your_action)wszystkich działań nawigacyjnych należy używać tylko funkcji kontrolera lub nawigować po nich.



1

Złapałem ten wyjątek po kilku zmianach nazw klas. Na przykład: miałem klasy wywołane FragmentAz @+is/fragment_aw wykresie nawigacyjnym i FragmentBz @+id/fragment_b. Następnie usunąłem FragmentAi zmieniłem nazwę FragmentBna FragmentA. Więc po tym węźle FragmentAnadal przebywał na wykresie nawigacji oraz android:namez FragmentB„s węzła została zmieniona path.to.FragmentA. Miałem dwa węzły z takimi samymi android:namei różnymi android:id, a akcja, której potrzebowałem, została zdefiniowana na węźle usuniętej klasy.


1

Przychodzi mi do głowy, gdy dwukrotnie wciskam przycisk Wstecz. Na początku przechwytuję KeyListeneri nadpisuję KeyEvent.KEYCODE_BACK. Dodałem poniższy kod w funkcji o nazwie OnResumeFragment, a następnie ta kwestia / problem jest rozwiązany.

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

Kiedy zdarza mi się to po raz drugi, a jego stan jest taki sam jak pierwszy, stwierdzam, że być może używam tej adsurdfunkcji. Przeanalizujmy te sytuacje.

  1. Najpierw FragmentA przechodzi do FragmentuB, następnie FragmentB nawiguje do FragmentuA, a następnie naciska przycisk Wstecz ... pojawia się awaria.

  2. Po drugie, FragmentA nawiguje do FragmentB, następnie FragmentB nawiguje do FragmentC, FragmentC nawiguje do FragmentuA, a następnie naciska przycisk Wstecz ... pojawia się awaria.

Myślę więc, że po naciśnięciu przycisku Wstecz FragmentA powróci do FragmentB lub FragmentC, a następnie spowoduje bałagan logowania. Wreszcie stwierdzam, że nazwana funkcja popBackStackmoże być używana raczej do nawigacji niż do nawigacji.

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

Jak dotąd problem jest naprawdę rozwiązany.


1

Wygląda na to, że mieszanie kontroli fragmentManagera z backstackiem i kontroli architektury nawigacji dla backstacka może również powodować ten problem.

Na przykład oryginalna podstawowa próbka CameraX wykorzystywała nawigację wsteczną fragmentManager, jak poniżej i wygląda na to, że nie współdziała poprawnie z nawigacją:

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

Jeśli zarejestrujesz `` bieżące miejsce docelowe '' w tej wersji przed przejściem z głównego fragmentu (w tym przypadku fragmentu kamery), a następnie zarejestrujesz go ponownie po powrocie do głównego fragmentu, możesz zobaczyć z id w logach, że id nie jest taki sam. Przypuszczalnie nawigacja zaktualizowała go podczas przechodzenia do fragmentu, a fragmntManager nie zaktualizował go ponownie podczas powrotu. Z dzienników:

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

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

Zaktualizowana wersja podstawowego przykładu CameraX korzysta z Nawigacji w następujący sposób:

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

Działa to poprawnie, a dzienniki pokazują ten sam identyfikator po powrocie do głównego fragmentu.

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

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

Podejrzewam, że morał tej historii, przynajmniej w tej chwili, polega na bardzo ostrożnym łączeniu nawigacji z nawigacją fragmentManager.


Brzmi to wiarygodnie, zbadam dalej. Czy ktoś był w stanie zweryfikować lub uzasadnić to twierdzenie?
Jerry Oka na

@JerryOkafor - Przetestowałem to w aplikacji, na której pracowałem, na podstawie CameraX Sample i zweryfikowałem, ale dobrze byłoby zobaczyć, czy ktoś inny też to widział. Właściwie przegapiłem `` nawigację wsteczną '' w jednym miejscu w tej samej aplikacji, więc niedawno ją poprawiłem.
Mick

1

Śmiesznym sposobem, ale bardzo potężnym jest: Po prostu nazwij to:

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

Po prostu utwórz to rozszerzenie:

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

1

Przyczyn tego problemu może być wiele. W moim przypadku korzystałem z modelu MVVM i obserwowałem wartość logiczną do nawigacji, gdy wartość logiczna jest prawdziwa -> nawiguj w przeciwnym razie nic nie rób i to działało dobrze, ale tutaj był jeden błąd

po naciśnięciu przycisku Wstecz z fragmentu docelowego napotkałem ten sam problem. i problemem był obiekt boolowski, ponieważ zapomniałem zmienić wartość boolean na false, co spowodowało bałagan. właśnie utworzyłem funkcję w viewModel, aby zmienić jej wartość na false i nazwał to zaraz po funkcji findNavController ()


1

Zwykle, gdy mi się to przytrafia, miałem problem opisany przez Charlesa Madere: dwa zdarzenia nawigacji wywołane w tym samym interfejsie użytkownika, jedno zmieniające currentDestination, a drugie kończy się niepowodzeniem, ponieważ currentDestination jest zmieniony. Może się to zdarzyć, jeśli dwukrotnie dotkniesz lub klikniesz dwa widoki z odbiornikiem kliknięć wywołującym findNavController.navigate.

Aby rozwiązać ten problem, możesz użyć if-check, try-catch lub, jeśli jesteś zainteresowany, istnieje funkcja findSafeNavController (), która sprawdza to za Ciebie przed nawigacją. Posiada również funkcję sprawdzania kłaczków, aby upewnić się, że nie zapomnisz o tym problemie.

GitHub

Artykuł szczegółowo opisujący problem


1

Jeśli klikniesz zbyt szybko, spowoduje to zerowanie i awarię.

Możemy użyć biblioteki RxBinding, aby pomóc w tym. Możesz dodać przyspieszenie i czas trwania kliknięcia, zanim to nastąpi.

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

Te artykuły o ograniczaniu przepustowości w systemie Android mogą pomóc. Twoje zdrowie!


1

Jeśli używasz widoku recyklingu, po prostu dodaj czas odnowienia nasłuchiwania kliknięć na kliknięciu, a także w pliku XML recyclinglerview android:splitMotionEvents="false"


1
Spójrz na odpowiedzi poniżej
Crazy

1

Po przemyśleniu rady Iana Lake'a w tym twitterze wymyśliłem następujące podejście. Po NavControllerWrapperzdefiniowaniu jako takie:

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

Następnie w kodzie nawigacyjnym:

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

1

Rozwiązuję ten problem, sprawdzając, czy następna akcja istnieje w bieżącym miejscu docelowym

public static void launchFragment(BaseFragment fragment, int action) {
    if (fragment != null && NavHostFragment.findNavController(fragment).getCurrentDestination().getAction(action) != null) {       
        NavHostFragment.findNavController(fragment).navigate(action);
    }
}

public static void launchFragment(BaseFragment fragment, NavDirections directions) {
    if (fragment != null && NavHostFragment.findNavController(fragment).getCurrentDestination().getAction(directions.getActionId()) != null) {       
        NavHostFragment.findNavController(fragment).navigate(directions);
    }
}

To rozwiązuje problem, jeśli użytkownik szybko kliknie przycisk 2 różne


0

Zdarzyło mi się to, moim problemem było klikanie przycisku FAB tab item fragment. Próbowałem przejść z jednego z fragmentów pozycji karty do another fragment.

Ale według Iana Lake'a w tej odpowiedzi musimy użyć tablayouti viewpager, żadnego wsparcia dla komponentów nawigacyjnych . Z tego powodu nie ma ścieżki nawigacji od tabeli zawierającej fragment do fragmentu pozycji karty.

dawny:

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

Rozwiązaniem było utworzenie ścieżki z układu zakładki zawierającej fragment do zamierzonego fragmentu np .: ścieżka: container fragment -> another fragment

Niekorzyść:

  • Wykres nawigacyjny nie przedstawia już dokładnie przepływu użytkowników.

0

W moim przypadku otrzymałem ten błąd, gdy próbowałem przejść z innego wątku, w 50% przypadków. Uruchom kod w głównym wątku pomaga

requireActivity().runOnUiThread {
    findNavController().navigate(...)
}

Chciałbym zobaczyć więcej głosów w tej sprawie, brzmi wiarygodnie, ale nie mogę tego zweryfikować.
Jerry Oka na

0

W moim przypadku miało to miejsce, gdy przypadkowo dodałem miejsce +docelowe w akcji, a awaria wystąpiła tylko wtedy, gdy wielokrotnie przeszedłem do tego samego fragmentu.

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

Rozwiązaniem jest usunięcie +z miejsca docelowego akcji, użyj tylko @id/profileFragmentzamiast@+id/profileFragment

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

0

Zaktualizowane rozwiązanie @Alex Nuts

Jeśli nie ma akcji dla konkretnego fragmentu i chcesz przejść do fragmentu

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

0

Napisałem te rozszerzenia

fun Fragment.navigateAction(action: NavDirections) {
    val navController = this.findNavController()
    if (navController.currentDestination?.getAction(action.actionId) == null) {
        return
    } else {
        navController.navigate(action)
    }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.