IllegalArgumentException: tujuan navigasi xxx tidak diketahui oleh NavController ini

139

Saya mengalami masalah dengan komponen Arsitektur Navigasi Android baru saat mencoba menavigasi dari satu Fragmen ke Fragmen lain , saya mendapatkan kesalahan aneh ini:

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

Setiap navigasi lainnya berfungsi dengan baik kecuali yang satu ini.

Saya menggunakan findNavController()fungsi Fragmen untuk mendapatkan akses ke NavController.

Bantuan apa pun akan dihargai.

Jerry Okafor
sumber
Berikan beberapa kode untuk pemahaman yang lebih baik.
Alex
12
Ini juga terjadi pada saya.
Eury Pérez Beltré
Sejauh ini tingkat kemunculan bug ini telah dikurangi dengan rilis perpustakaan yang lebih baru, tetapi menurut saya perpustakaan tersebut belum terdokumentasi dengan baik.
Jerry Okafor

Jawaban:

76

Dalam kasus saya, jika pengguna mengklik tampilan yang sama dua kali dengan sangat cepat, crash ini akan terjadi. Jadi, Anda perlu menerapkan semacam logika untuk mencegah beberapa klik cepat ... Yang sangat mengganggu, tetapi tampaknya perlu.

Anda dapat membaca lebih lanjut tentang mencegah ini di sini: Android Mencegah Klik Dua Kali Tombol

Sunting 19/3/2019 : Hanya untuk memperjelas lebih jauh, kerusakan ini tidak secara eksklusif dapat direkonstruksi hanya dengan "mengklik tampilan yang sama dua kali dengan sangat cepat". Cara lainnya, Anda dapat menggunakan dua jari dan mengeklik dua (atau lebih) tampilan pada saat yang bersamaan, di mana setiap tampilan memiliki navigasi sendiri yang akan mereka lakukan. Ini sangat mudah dilakukan jika Anda memiliki daftar item. Info di atas tentang pencegahan beberapa klik akan menangani kasus ini.

Sunting 16/4/2020 : Seandainya Anda tidak terlalu tertarik untuk membaca postingan Stack Overflow di atas, saya menyertakan solusi (Kotlin) saya sendiri yang telah lama saya gunakan sekarang.

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
sumber
23
Pengeditan tentang menggunakan 2 jari dan mengklik 2 tampilan pada saat yang bersamaan! Itulah kuncinya bagi saya dan membantu saya mereplikasi masalah dengan mudah. Pembaruan hebat dengan informasi itu.
Richard Le Mesurier
Selama fase debug saya kebetulan mengklik saat aplikasi macet menunggu untuk melanjutkan eksekusi. Sepertinya kasus lain dari dua klik berikutnya berturut-turut ke IDE
Marco
1
Terima kasih untuk ini. Menyelamatkan saya dari beberapa crash dan beberapa garukan kepala :)
user2672052
58

Periksa currentDestinationsebelum menelepon navigasi mungkin bisa membantu.

Misalnya, jika Anda memiliki dua tujuan fragmen pada grafik navigasi fragmentAdan fragmentB, dan hanya ada satu tindakan dari fragmentAsampai fragmentB. menelepon navigate(R.id.action_fragmentA_to_fragmentB)akan menghasilkan IllegalArgumentExceptionsaat Anda sudah aktif fragmentB. Oleh karena itu, Anda harus selalu memeriksa currentDestinationsebelum melakukan navigasi.

if (navController.currentDestination?.id == R.id.fragmentA) {
    navController.navigate(R.id.action_fragmentA_to_fragmentB)
}
theJian
sumber
3
Saya memiliki aplikasi pencarian yang menavigasi dengan tindakan dengan argumen. Dengan demikian itu bisa menavigasi dari currentDestination ke dirinya sendiri. Saya akhirnya melakukan hal yang sama kecuali navController.currentDestination == navController.graph.node. Rasanya agak kotor dan saya merasa saya tidak perlu melakukan ini.
Shawn Maybush
85
Perpustakaan seharusnya tidak memaksa kita untuk melakukan pemeriksaan ini, ini memang konyol.
DaniloDeQueiroz
Saya memiliki masalah yang sama. Saya memiliki EditText, dan tombol 'simpan' untuk menyimpan konten EditText dalam database. Itu selalu macet saat menekan tombol 'simpan'. Saya menduga bahwa alasannya ada hubungannya dengan fakta bahwa, untuk dapat menekan tombol 'simpan', saya harus menyingkirkan keyboard di layar dengan mengetuk tombol kembali.
The Fox
Ini memeriksa kondisi kesalahan, tetapi tidak menyelesaikan masalah. Menariknya, kondisi ini benar jika navigasi backstack menjadi kosong karena alasan yang tidak diinginkan.
Mike76
1
bahkan di iOS meskipun terkadang beberapa ViewController didorong saat Anda menekan tombol beberapa kali. Tebak Android dan iOS memiliki masalah ini.
coolcool1994
47

Anda dapat memeriksa tindakan yang diminta di tujuan pengontrol navigasi saat ini.

UPDATE menambahkan penggunaan tindakan global untuk navigasi yang aman.

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
sumber
1
Solusi ini tidak akan berfungsi untuk tindakan apa pun yang ditentukan di luar currentDestinationdaftar tindakan. Katakanlah Anda memiliki tindakan global yang ditentukan dan menggunakan tindakan itu untuk menavigasi. Ini akan gagal karena tindakan tidak ditentukan dalam daftar <action> currentDestination. Menambahkan tanda centang seperti currentDestination?.getAction(resId) != null || currentDestination?.id != resIdseharusnya menyelesaikannya tetapi juga mungkin tidak mencakup setiap kasus.
wchristiansen
@wchristiansen, terima kasih atas catatannya. Saya telah memperbarui kode dengan penggunaan tindakan global
Alex Nuts
@AlexNuts jawaban yang bagus. Saya pikir Anda dapat menghapus ?: graph.getAction(resId)-> currentDestination?.getAction(resId)akan mengembalikan tindakan untuk tindakan Global atau non-Global (saya telah mengujinya). Juga, akan lebih baik jika Anda menggunakan Safe Args -> daripada meneruskan navDirections: NavDirectionsdaripada resIddan argssecara terpisah.
Wess
@AlexNuts Perhatikan bahwa solusi ini tidak mendukung navigasi ke tujuan yang sama dengan tujuan saat ini. Tidak mungkin menavigasi dari tujuan X dengan Bundel Y ke tujuan X dengan Bundel Z.
Wess
18

Ini juga bisa terjadi jika Anda memiliki Fragmen A dengan ViewPager dari Fragmen B Dan Anda mencoba menavigasi dari B ke C

Karena di ViewPager, fragmen bukan tujuan A, grafik Anda tidak akan tahu bahwa Anda berada di B.

Solusinya adalah menggunakan ADirect di B untuk menavigasi ke C

AntPachon
sumber
Dalam hal ini, crash tidak terjadi setiap saat tetapi hanya terjadi sangat jarang. Bagaimana cara mengatasinya?
Srikar Reddy
Anda dapat menambahkan aksi global di dalam navGraph dan menggunakannya untuk menavigasi
Abraham Mathew
1
Karena B tidak perlu mengetahui induk persisnya, akan lebih baik menggunakan ADirections melalui antarmuka seperti (parentFragment as? XActionListener)?.Xaction()dan perhatikan bahwa Anda dapat memegang fungsi ini sebagai variabel lokal jika itu berguna
hmac
bisakah Anda membagikan contoh kode untuk menggambarkan ini karena saya memiliki masalah yang sama
Ikhiloya Imokhai
siapa pun bisa plz kode sampel, saya terjebak pada masalah yang sama. Punya fragmen dan kemudian tabfragmen
Usman Zafer
13

Apa yang saya lakukan untuk mencegah crash adalah sebagai berikut:

Saya memiliki BaseFragment, di sana saya telah menambahkan ini fununtuk memastikan bahwa destinationdiketahui oleh currentDestination:

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

Perlu dicatat bahwa saya menggunakan plugin SafeArgs .

Douglas Kazumi
sumber
12

Dalam kasus saya, saya menggunakan tombol kembali khusus untuk menavigasi ke atas. Saya menelepon onBackPressed()sebagai ganti kode berikut

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

Hal ini menyebabkan IllegalArgumentExceptionterjadinya. Setelah saya mengubahnya untuk menggunakan navigateUp()metode sebagai gantinya, saya tidak mengalami crash lagi.

Neil
sumber
Saya tidak mengerti apa perbedaan antara onBackPressed dan ini, masih terjebak dengan tombol kembali sistem dan menimpanya dan menggantinya dengan ini tampaknya gila
Daniel Wilson
2
Saya setuju itu memang terlihat gila. Banyak hal yang saya temui di komponen arsitektur navigasi android terasa agak gila, IMO diatur terlalu kaku. Berpikir untuk melakukan implementasi saya sendiri untuk proyek kami karena itu hanya membuat terlalu banyak sakit kepala
Neil
Tidak berhasil untuk saya ... Masih mendapatkan kesalahan yang sama.
Otziii
5

TL; DR Tutup navigatepanggilan Anda dengan try-catch(cara sederhana), atau pastikan hanya akan ada satu panggilan navigatedalam waktu singkat. Masalah ini sepertinya tidak akan hilang. Salin cuplikan kode yang lebih besar di aplikasi Anda dan coba.

Halo. Berdasarkan beberapa tanggapan bermanfaat di atas, saya ingin membagikan solusi saya yang dapat diperpanjang.

Berikut adalah kode yang menyebabkan kerusakan ini di aplikasi saya:

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

Cara untuk mereproduksi bug dengan mudah adalah dengan mengetuk dengan banyak jari pada daftar item di mana klik pada setiap item diselesaikan dalam navigasi ke layar baru (pada dasarnya sama dengan yang dicatat orang - dua klik atau lebih dalam waktu yang sangat singkat ). Aku tahu itu:

  1. navigateDoa pertama selalu bekerja dengan baik;
  2. Kedua dan semua pemanggilan navigatemetode lainnya diselesaikan IllegalArgumentException.

Dari sudut pandang saya, situasi ini mungkin sangat sering muncul. Karena pengulangan kode adalah praktik yang buruk dan selalu baik untuk memiliki satu titik pengaruh, saya memikirkan solusi berikutnya:

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

}

Dan dengan demikian kode di atas hanya berubah dalam satu baris dari ini:

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

untuk ini:

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

Bahkan menjadi sedikit lebih pendek. Kode tersebut diuji di tempat yang tepat di mana kerusakan terjadi. Tidak mengalaminya lagi, dan akan menggunakan solusi yang sama untuk navigasi lain untuk menghindari kesalahan yang sama lebih jauh.

Semua pikiran diterima!

Apa sebenarnya penyebab kecelakaan itu

Ingatlah bahwa di sini kita bekerja dengan grafik navigasi, pengontrol navigasi dan back-stack yang sama ketika kita menggunakan metode Navigation.findNavController.

Kami selalu mendapatkan pengontrol dan grafik yang sama di sini. When navigate(R.id.my_next_destination)dipanggil graph dan back-stack berubah hampir seketika sementara UI belum diperbarui. Hanya saja tidak cukup cepat, tapi tidak apa-apa. Setelah back-stack berubah, sistem navigasi menerima navigate(R.id.my_next_destination)panggilan kedua . Karena back-stack telah berubah, kami sekarang beroperasi relatif terhadap fragmen teratas dalam tumpukan. Fragmen teratas adalah fragmen yang Anda R.id.my_next_destinationtuju dengan menggunakan , tetapi tidak berisi tujuan selanjutnya dengan ID R.id.my_next_destination. Jadi, Anda mendapatkan IllegalArgumentExceptionkarena ID yang tidak diketahui oleh fragmen.

Kesalahan persis ini dapat ditemukan dalam NavController.javametode findDestination.

Jenea Vranceanu
sumber
4

Dalam kasus saya, masalah terjadi saat saya menggunakan kembali salah satu Fragmen saya di dalam sebuah viewpagerfragmen sebagai anak dari viewpager. The viewpagerFragment (yang merupakan fragmen induk) ditambahkan dalam xml Navigasi, tetapi tindakan itu tidak ditambahkan dalam viewpagerfragmen induk.

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

Memperbaiki masalah dengan menambahkan tindakan ke fragmen viewpager induk juga seperti yang ditunjukkan di bawah ini:

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

Hari ini

def navigationVersion = "2.2.1"

Masalahnya masih ada. Pendekatan saya tentang Kotlin adalah:

// 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
sumber
4

Anda dapat memeriksa sebelum menavigasi apakah Fragmen yang meminta navigasi masih merupakan tujuan saat ini, diambil dari inti ini .

Ini pada dasarnya menetapkan tag pada fragmen untuk pencarian nanti.

/**
 * 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 hanyalah sebuah id yang harus Anda tambahkan ke ids.xml Anda, untuk memastikannya unik. <item name="tag_navigation_destination_id" type="id" />

Info selengkapnya tentang bug dan solusinya, serta navigateSafe(...)metode ekstensi di "Memperbaiki“… yang tidak diketahui oleh NavController ini ”

jujur
sumber
Saya telah mempelajari beberapa solusi berbeda untuk masalah ini, dan solusi Anda pasti yang terbaik. Membuat saya sedih melihat begitu sedikit cinta untuk itu
Lukas
1
mungkin berguna untuk membuat pengenal unik sebagai pengganti NAV_DESTINATION_IDdengan sesuatu seperti ini stackoverflow.com/a/15021758/1572848
William Reed
ya, saya memperbarui jawabannya
Frank
Dari mana tag itu berasal dan mengapa itu diperlukan? Saya memiliki masalah di mana id sebenarnya pada komponen navigasi tidak cocok dengan ini R.id.
riezebosch
R.id.tag_navigation_destination_idhanyalah sebuah id yang harus Anda tambahkan ke ids.xml Anda, untuk memastikannya unik. <item name="tag_navigation_destination_id" type="id" />
Frank
3

Dalam kasus saya, saya memiliki beberapa file grafik nav dan saya mencoba untuk berpindah dari 1 lokasi grafik nav ke tujuan di grafik nav lain.

Untuk ini kita harus menyertakan grafik nav ke-2 di yang pertama seperti ini

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

dan tambahkan ini ke tindakan Anda:

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

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

di grafik kedua.

Info selengkapnya di sini

hushed_voice
sumber
3

Saya telah menyelesaikan masalah yang sama dengan memberi centang sebelum menavigasi alih-alih kode boilerplate untuk mengklik kontrol langsung

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

menurut jawaban ini

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

noyan
sumber
2

Dalam kasus saya, bug terjadi karena saya memiliki tindakan navigasi dengan Single Topdan Clear Taskopsi diaktifkan setelah layar splash.

Eury Pérez Beltré
sumber
1
Tetapi clearTask tidak digunakan lagi, Anda harus menggunakan popUpTo () sebagai gantinya.
Jerry Okafor
@ Po10cio Tidak ada satu pun dari bendera itu yang dibutuhkan, saya baru saja menghapusnya dan itu telah diperbaiki.
Eury Pérez Beltré
2

Saya mendapat kesalahan yang sama karena saya menggunakan Panel Samping Navigasi dan getSupportFragmentManager().beginTransaction().replace( )pada saat yang sama di suatu tempat dalam kode saya.

Saya menghilangkan kesalahan dengan menggunakan kondisi ini (menguji apakah tujuan):

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

Dalam kasus saya, kesalahan sebelumnya dipicu ketika saya mengklik opsi panel samping navigasi. Pada dasarnya kode di atas memang menyembunyikan kesalahan, karena dalam kode saya di suatu tempat saya menggunakan navigasi menggunakan getSupportFragmentManager().beginTransaction().replace( )kondisi -

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

tidak pernah tercapai karena (Navigation.findNavController(v).getCurrentDestination().getId()selalu poiting to home fragment. Anda hanya boleh menggunakan Navigation.findNavController(v).navigate(R.id.your_action)atau nav fungsi pengontrol grafik untuk semua tindakan navigasi Anda.

Aness
sumber
1

Saya menangkap pengecualian ini setelah beberapa kali mengganti nama kelas. Sebagai contoh: Saya memiliki kelas yang dipanggil FragmentAdengan @+is/fragment_adalam grafik navigasi dan FragmentBdengan @+id/fragment_b. Lalu saya hapus FragmentAdan ganti namanya FragmentBmenjadi FragmentA. Jadi setelah node yang dari FragmentAmasih tinggal di grafik navigasi, dan android:namedari FragmentB's simpul diubah namanya path.to.FragmentA. Saya memiliki dua node dengan yang sama android:namedan berbeda android:id, dan tindakan yang saya butuhkan ditentukan pada node kelas yang dihapus.

VasyaDariRusia
sumber
1

Itu terjadi pada saya ketika saya menekan tombol kembali dua kali. Pada awalnya, saya mencegat KeyListenerdan menimpa KeyEvent.KEYCODE_BACK. Saya menambahkan kode di bawah ini dalam fungsi bernama OnResumeuntuk Fragmen, dan kemudian pertanyaan / masalah ini terpecahkan.

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

Ketika itu terjadi pada saya untuk kedua kalinya, dan statusnya sama dengan yang pertama, saya menemukan bahwa saya mungkin menggunakan adsurdfungsinya. Mari kita analisis situasi ini.

  1. Pertama, FragmentA menavigasi ke FragmentB, lalu FragmentB menavigasi ke FragmentA, lalu tekan tombol kembali ... kerusakan muncul.

  2. Kedua, FragmentA menavigasi ke FragmentB, lalu FragmentB menavigasi ke FragmentC, FragmentC menavigasi ke FragmentA, lalu tekan tombol kembali ... kerusakan muncul.

Jadi menurut saya saat menekan tombol back, FragmentA akan kembali ke FragmentB atau FragmentC, lalu menyebabkan login berantakan. Akhirnya saya menemukan bahwa fungsi bernama popBackStackdapat digunakan untuk mundur daripada menavigasi.

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

Sejauh ini, masalahnya benar-benar terpecahkan.

唐德坤
sumber
1

Tampaknya mencampurkan kontrol fragmentManager dari backstack dan kontrol Arsitektur Navigasi dari backstack juga dapat menyebabkan masalah ini.

Misalnya, contoh dasar CameraX asli menggunakan navigasi backstack fragmentManager seperti di bawah ini dan tampak seolah-olah tidak berinteraksi dengan benar dengan Navigasi:

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

Jika Anda mencatat 'tujuan saat ini' dengan versi ini sebelum berpindah dari fragmen utama (dalam kasus ini, fragmen kamera) dan kemudian mencatatnya lagi saat Anda kembali ke fragmen utama, Anda dapat melihat dari id di log bahwa id tidak sama. Saat menebak, Navigation memperbaruinya saat berpindah ke fragmen dan fragmntManager tidak mengupdatenya lagi saat kembali. Dari log:

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

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

Versi terbaru dari contoh dasar CameraX menggunakan Navigasi untuk mengembalikan seperti ini:

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

Ini bekerja dengan benar dan log menunjukkan id yang sama ketika kembali ke fragmen utama.

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

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

Saya menduga moral dari cerita ini, setidaknya saat ini, adalah sangat berhati-hati dalam mencampurkan Navigasi dengan navigasi fragmentManager.

Mick
sumber
Kedengarannya masuk akal, saya akan menyelidiki lebih lanjut. Adakah yang bisa memverifikasi atau mendukung klaim ini?
Jerry Okafor
@JerryOkafor - Saya telah mengujinya di aplikasi yang saya kerjakan berdasarkan Sampel CameraX dan memverifikasinya, tetapi alangkah baiknya untuk melihat apakah orang lain juga telah melihat ini. Saya benar-benar melewatkan 'navigasi belakang' di satu tempat di aplikasi yang sama jadi baru saja memperbaikinya lagi baru-baru ini.
Mick
1

Cara yang konyol tapi sangat ampuh adalah: Sebut saja ini:

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

Buat saja Ekstensi ini:

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

Mungkin ada banyak alasan untuk masalah ini. Dalam kasus saya, saya menggunakan model MVVM dan saya mengamati boolean untuk navigasi ketika boolean itu benar -> navigasi yang lain jangan lakukan apa-apa dan ini berfungsi dengan baik tetapi ada satu kesalahan di sini

ketika menekan tombol kembali dari fragmen tujuan saya mengalami masalah yang sama. Dan masalahnya adalah objek boolean karena saya lupa mengubah nilai boolean menjadi false ini menciptakan kekacauan. Saya baru saja membuat fungsi di viewModel untuk mengubah nilainya menjadi false dan memanggilnya tepat setelah findNavController ()

Manoj Chouhan
sumber
1

Biasanya ketika ini terjadi pada saya, saya memiliki masalah yang dijelaskan oleh Charles Madere: Dua peristiwa navigasi dipicu pada ui yang sama, satu mengubah currentDestination dan yang lainnya gagal karena currentDestination diubah. Ini dapat terjadi jika Anda mengetuk dua kali, atau mengeklik dua tampilan dengan pendengar klik yang memanggil findNavController.navigate.

Jadi untuk mengatasi ini, Anda dapat menggunakan if-check, try-catch, atau jika Anda tertarik, ada findSafeNavController () yang melakukan pemeriksaan ini untuk Anda sebelum menavigasi. Ini juga memiliki pemeriksaan lint untuk memastikan Anda tidak melupakan masalah ini.

GitHub

Artikel yang merinci masalah

Gergely Hegedus
sumber
1

Jika Anda mengklik terlalu cepat, itu akan menyebabkan null dan crash.

Kami dapat menggunakan RxBinding lib untuk membantu dalam hal ini. Anda dapat menambahkan throttle dan durasi pada klik sebelum itu terjadi.

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

Ini artikel tentang throttling pada Android kekuatan bantuan. Bersulang!

Joshua
sumber
1

Jika Anda menggunakan recyclerview, cukup tambahkan cooldown click listener pada klik Anda dan juga dalam penggunaan file xml recyclerview Anda android:splitMotionEvents="false"

Gila
sumber
1
Lihatlah jawaban di bawah ini
Gila
1

Setelah memikirkan saran Danau Ian di utas twitter ini, saya datang dengan pendekatan berikut. Setelah NavControllerWrapperdidefinisikan seperti itu:

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

Kemudian di kode navigasi:

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

Ini terjadi pada saya, masalah saya adalah saya mengklik FAB tab item fragment. Saya mencoba menavigasi dari salah satu fragmen item tab ke another fragment.

Tetapi menurut Danau Ian dalam jawaban ini kita harus menggunakan tablayoutdan viewpager, tidak ada komponen pendukung navigasi . Karenanya, tidak ada jalur navigasi dari tablayout yang berisi fragmen ke item tab.

ex:

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

Solusinya adalah membuat jalur dari tata letak tab yang berisi fragmen ke fragmen yang diinginkan misalnya: jalur: container fragment -> another fragment

Kerugian:

  • Grafik Nav tidak lagi merepresentasikan alur pengguna secara akurat.
pengguna158
sumber
0

Dalam kasus saya, saya mendapat kesalahan itu ketika mencoba menavigasi dari utas lain, dalam 50% kasus. Jalankan kode pada thread utama membantu

requireActivity().runOnUiThread {
    findNavController().navigate(...)
}
Tautan182
sumber
Saya ingin melihat lebih banyak suara tentang ini, terdengar masuk akal tetapi saya tidak dapat memverifikasinya.
Jerry Okafor
0

Dalam kasus saya ini terjadi ketika saya tidak sengaja menambahkan +tujuan dalam tindakan, dan crash hanya terjadi ketika saya pergi ke fragmen yang sama beberapa kali.

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

Solusinya adalah untuk menghapus +dari tujuan tindakan, gunakan hanya @id/profileFragmentsebagai pengganti@+id/profileFragment

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

Memperbarui solusi @Alex Nuts

Jika tidak ada tindakan untuk fragmen tertentu dan ingin membuka fragmen

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

Saya menulis ekstensi ini

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

Saya membuat fungsi ekstensi ini untuk Fragmen:

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
sumber