IllegalArgumentException: การนำทาง xxx ไม่รู้จักกับ NavController นี้


139

ฉันมีปัญหากับองค์ประกอบสถาปัตยกรรมการนำทาง Android ใหม่เมื่อฉันพยายามนำทางจากส่วนหนึ่งไปยังอีกส่วนหนึ่งฉันได้รับข้อผิดพลาดแปลก ๆ นี้:

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

การนำทางอื่น ๆ ทั้งหมดทำงานได้ดียกเว้นอันนี้โดยเฉพาะ

ฉันใช้findNavController()ฟังก์ชันของ Fragment เพื่อเข้าถึงไฟล์NavController.

ความช่วยเหลือใด ๆ ที่จะได้รับการชื่นชม.


โปรดระบุรหัสเพื่อความเข้าใจที่ดีขึ้น
Alex

12
สิ่งนี้ก็เกิดขึ้นกับฉันเช่นกัน
Eury PérezBeltré

จนถึงขณะนี้อัตราการเกิดข้อบกพร่องนี้ลดลงเมื่อมีไลบรารีรุ่นใหม่ ๆ แต่ฉันคิดว่าห้องสมุดยังไม่ได้รับการจัดทำเป็นเอกสารที่ดี
Jerry Okafor

คำตอบ:


76

ในกรณีของฉันถ้าผู้ใช้คลิกมุมมองเดียวกันสองครั้งอย่างรวดเร็วความผิดพลาดนี้จะเกิดขึ้น ดังนั้นคุณต้องใช้ตรรกะบางอย่างเพื่อป้องกันการคลิกอย่างรวดเร็วหลายครั้ง ... ซึ่งน่ารำคาญมาก แต่ดูเหมือนจะจำเป็น

คุณสามารถอ่านข้อมูลเพิ่มเติมเกี่ยวกับการป้องกันได้ที่นี่: Android การป้องกันการดับเบิลคลิกที่ปุ่ม A

แก้ไข 19/3/2019 : เพื่อชี้แจงเพิ่มเติมเล็กน้อยข้อขัดข้องนี้ไม่สามารถทำซ้ำได้โดยเพียงแค่ "คลิกมุมมองเดิมสองครั้งอย่างรวดเร็ว" หรือคุณสามารถใช้สองนิ้วและคลิกสองมุมมอง (หรือมากกว่า) พร้อมกันโดยที่แต่ละมุมมองจะมีการนำทางของตัวเองที่จะดำเนินการ นี่คือโดยเฉพาะอย่างยิ่งง่ายที่จะทำเมื่อคุณมีรายชื่อของรายการ ข้อมูลข้างต้นเกี่ยวกับการป้องกันการคลิกหลายครั้งจะจัดการกับกรณีนี้

แก้ไข 16/4/2020 : ในกรณีที่คุณไม่สนใจมากที่จะอ่านโพสต์ Stack Overflow ด้านบนฉันรวมโซลูชัน (Kotlin) ของฉันเองที่ฉันใช้มานานแล้ว

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
แก้ไขเกี่ยวกับการใช้ 2 นิ้วและคลิก 2 มุมมองในเวลาเดียวกัน! นั่นคือกุญแจสำคัญสำหรับฉันและช่วยให้ฉันทำซ้ำปัญหาได้อย่างง่ายดาย การอัปเดตที่ยอดเยี่ยมด้วยข้อมูลนั้น
Richard Le Mesurier

ในระหว่างขั้นตอนการดีบักฉันเกิดคลิกขึ้นในขณะที่แอปค้างอยู่เพื่อรอให้ดำเนินการต่อ ดูเหมือนจะเป็นอีกกรณีหนึ่งของการคลิกครั้งต่อ ๆ มาสองครั้งติดต่อกันใน IDE
Marco

1
ขอบคุณสำหรับสิ่งนี้. ช่วยฉันไม่กี่ข้อขัดข้องและเกาหัว :)
user2672052

58

ตรวจสอบcurrentDestinationก่อนโทรนำทางอาจเป็นประโยชน์

ตัวอย่างเช่นถ้าคุณมีสองสถานที่ท่องเที่ยวส่วนบนกราฟนำทางfragmentAและfragmentBและมีเพียงหนึ่งการกระทำจากไปfragmentA fragmentBเรียกร้องnavigate(R.id.action_fragmentA_to_fragmentB)จะมีผลในเมื่อคุณมีอยู่แล้วในIllegalArgumentException fragmentBดังนั้นคุณควรตรวจสอบcurrentDestinationก่อนนำทางทุกครั้ง

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

3
ฉันมีแอปค้นหาที่นำทางด้วยการดำเนินการที่มีอาร์กิวเมนต์ ดังนั้นจึงสามารถนำทางจาก currentDestination ไปยังตัวมันเอง ฉันลงเอยด้วยการทำเช่นเดียวกันยกเว้น navController.currentDestination == navController.graph.node มันรู้สึกค่อนข้างสกปรกและฉันรู้สึกว่าไม่ควรทำแบบนี้
Shawn Maybush

86
ห้องสมุดไม่ควรบังคับให้เราทำการตรวจสอบนี้มันเป็นเรื่องที่ไร้สาระจริงๆ
DaniloDeQueiroz

ฉันมีปัญหาเดียวกัน ฉันมี EditText และปุ่ม 'บันทึก' เพื่อเก็บเนื้อหาของ EditText ในฐานข้อมูล มันมักจะล้มเหลวเมื่อกดปุ่ม "บันทึก" ฉันสงสัยว่าเหตุผลนั้นเกี่ยวข้องกับความจริงที่ว่าเพื่อให้สามารถกดปุ่ม 'บันทึก' ได้ฉันจำเป็นต้องกำจัดแป้นพิมพ์บนหน้าจอโดยแตะที่ปุ่มย้อนกลับ
The Fox

สิ่งนี้จะตรวจสอบเงื่อนไขข้อผิดพลาด แต่ไม่สามารถแก้ปัญหาได้ ที่น่าสนใจคือเงื่อนไขนี้เป็นจริงหากแบ็คสแต็คการนำทางว่างเปล่าเนื่องจากสาเหตุที่ไม่ต้องการ
Mike76

1
แม้ใน iOS แม้ว่าบางครั้ง ViewController หลายตัวจะถูกผลักเมื่อคุณกดปุ่มหลาย ๆ ครั้ง เดาว่าทั้ง Android และ iOS มีปัญหานี้
coolcool1994

47

คุณสามารถตรวจสอบการดำเนินการที่ร้องขอได้ในปลายทางปัจจุบันของตัวควบคุมการนำทาง

UPDATE เพิ่มการใช้งานการดำเนินการทั่วโลกเพื่อการนำทางที่ปลอดภัย

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
โซลูชันนี้จะใช้ไม่ได้กับการดำเนินการใด ๆ ที่กำหนดไว้ภายนอกcurrentDestinationรายการการดำเนินการของ สมมติว่าคุณมีการดำเนินการทั่วโลกที่กำหนดไว้และใช้การกระทำนั้นเพื่อนำทาง การดำเนินการนี้จะล้มเหลวเนื่องจากการดำเนินการไม่ได้กำหนดไว้ในรายการ <action> ของ currentDestination การเพิ่มการตรวจสอบเช่นcurrentDestination?.getAction(resId) != null || currentDestination?.id != resIdจะช่วยแก้ปัญหาได้ แต่อาจไม่ครอบคลุมทุกกรณี
wchristiansen

@wchristiansen ขอบคุณสำหรับบันทึก ฉันได้อัปเดตโค้ดโดยใช้การดำเนินการทั่วโลก
Alex Nuts

@AlexNuts คำตอบที่ดี ฉันคิดว่าคุณสามารถลบ?: graph.getAction(resId)-> currentDestination?.getAction(resId)จะส่งคืนการดำเนินการสำหรับทั้งการกระทำทั่วโลกหรือที่ไม่ใช่ทั่วโลก (ฉันได้ทดสอบแล้ว) นอกจากนี้จะดีกว่าถ้าคุณทำให้การใช้หาเรื่อง s ปลอดภัย -> ค่อนข้างผ่านในnavDirections: NavDirectionsกว่าresIdและargsแยก
Wess

@AlexNuts หมายเหตุโซลูชันนี้ไม่สนับสนุนการนำทางไปยังปลายทางเดียวกันกับปลายทางปัจจุบัน Iow การนำทางจากปลายทาง X พร้อม Bundle Y ไปยังปลายทาง X ด้วย Bundle Z เป็นไปไม่ได้
Wess

18

นอกจากนี้ยังอาจเกิดขึ้นได้หากคุณมี Fragment A พร้อม ViewPager ของ Fragments B และคุณพยายามนำทางจาก B ไป C

เนื่องจากใน ViewPager ชิ้นส่วนไม่ใช่ปลายทางของ A กราฟของคุณจะไม่รู้ว่าคุณอยู่บน B

วิธีแก้ปัญหาสามารถใช้ ADirections ใน B เพื่อนำทางไปยัง C


ในกรณีนี้การชนไม่ได้เกิดขึ้นทุกครั้ง แต่จะเกิดขึ้นน้อยมากเท่านั้น ต้องแก้ยังไง?
Srikar Reddy

คุณสามารถเพิ่มการดำเนินการทั่วโลกใน navGraph และใช้เพื่อนำทาง
Abraham Mathew

1
เนื่องจาก B ไม่จำเป็นต้องตระหนักถึงผู้ปกครองที่แน่นอนจึงควรใช้ ADirections ผ่านอินเทอร์เฟซเช่น(parentFragment as? XActionListener)?.Xaction()และโปรดทราบว่าคุณสามารถถือฟังก์ชันนี้เป็นตัวแปรท้องถิ่นได้หากมีประโยชน์
hmac

คุณช่วยแบ่งปันรหัสตัวอย่างเพื่อแสดงให้เห็นได้
ไหม

ใครสามารถขอรหัสตัวอย่างได้ฉันติดปัญหาเดียวกัน มีส่วนและส่วนแท็บ
Usman Zafer

13

สิ่งที่ฉันทำเพื่อป้องกันความผิดพลาดมีดังต่อไปนี้:

ฉันมี BaseFragment ในนั้นฉันได้เพิ่มสิ่งนี้funเพื่อให้แน่ใจว่าdestinationเป็นที่รู้จักโดยcurrentDestination:

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

น่าสังเกตว่าฉันกำลังใช้ปลั๊กอินSafeArgs


12

ในกรณีของฉันฉันใช้ปุ่มย้อนกลับที่กำหนดเองเพื่อนำทางขึ้น ฉันเรียกonBackPressed()แทนรหัสต่อไปนี้

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

สิ่งนี้ทำให้IllegalArgumentExceptionเกิดขึ้น หลังจากที่ฉันเปลี่ยนมาใช้navigateUp()วิธีการแทนฉันก็ไม่เกิดความผิดพลาดอีกเลย


ฉันไม่เข้าใจความแตกต่างระหว่าง onBackPressed กับสิ่งนี้ยังคงติดอยู่กับปุ่มย้อนกลับของระบบและการแทนที่และแทนที่ด้วยสิ่งนี้ดูเหมือนจะบ้า
Daniel Wilson

2
ฉันยอมรับว่ามันดูบ้า หลายสิ่งที่ฉันเคยพบในส่วนประกอบสถาปัตยกรรมการนำทางของ Android รู้สึกบ้าไปหน่อยมันตั้งค่า IMO ที่เข้มงวดเกินไป คิดถึงการดำเนินการของตัวเองสำหรับโครงการของเราเพราะมันสร้างความปวดหัวมากเกินไป
นีล

ไม่ได้ผลสำหรับฉัน ... ยังคงได้รับข้อผิดพลาดเดียวกัน
Otziii

6

TL; DRตัดnavigateสายของคุณด้วยtry-catch(วิธีง่ายๆ) หรือตรวจสอบให้แน่ใจว่าจะมีการโทรเพียงครั้งเดียวnavigateในช่วงเวลาสั้น ๆ ปัญหานี้จะไม่หายไป คัดลอกข้อมูลโค้ดที่ใหญ่กว่าในแอปของคุณแล้วลองใช้

สวัสดี. จากคำตอบที่เป็นประโยชน์สองสามข้อข้างต้นฉันต้องการแบ่งปันวิธีแก้ปัญหาของฉันที่สามารถขยายได้

นี่คือรหัสที่ทำให้เกิดข้อผิดพลาดในแอปพลิเคชันของฉัน:

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

วิธีที่จะทำให้เกิดข้อผิดพลาดได้อย่างง่ายดายคือการแตะด้วยหลาย ๆ นิ้วในรายการที่การคลิกแต่ละรายการจะแก้ไขในการนำทางไปยังหน้าจอใหม่ (โดยทั่วไปจะเหมือนกับที่ผู้คนสังเกตเห็นคือการคลิกสองครั้งขึ้นไปในช่วงเวลาสั้น ๆ ) ฉันสังเกตว่า:

  1. การnavigateเรียกครั้งแรกใช้งานได้ดีเสมอ
  2. คำเรียกร้องที่สองและอื่น ๆ ทั้งหมดของnavigateวิธีการแก้ไขในIllegalArgumentException.

จากมุมมองของฉันสถานการณ์นี้อาจปรากฏบ่อยมาก เนื่องจากการใช้รหัสซ้ำเป็นการปฏิบัติที่ไม่ดีและเป็นเรื่องดีที่จะมีอิทธิพลเพียงจุดเดียวฉันจึงคิดหาวิธีแก้ไขปัญหาต่อไป:

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

}

ดังนั้นรหัสด้านบนจึงเปลี่ยนเพียงบรรทัดเดียวจากนี้:

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

สำหรับสิ่งนี้:

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

มันก็สั้นลงเล็กน้อย รหัสได้รับการทดสอบในสถานที่ที่เกิดข้อขัดข้อง ไม่พบปัญหานี้อีกต่อไปและจะใช้โซลูชันเดียวกันสำหรับการนำทางอื่น ๆ เพื่อหลีกเลี่ยงข้อผิดพลาดเดียวกันเพิ่มเติม

ยินดีต้อนรับความคิดใด ๆ !

อะไรคือสาเหตุของความผิดพลาด

Navigation.findNavControllerโปรดจำไว้ว่าที่นี่เราทำงานกับกราฟนำทางเดียวกันควบคุมการนำและด้านหลังกองเมื่อเราใช้วิธีการ

เรามักจะได้ตัวควบคุมและกราฟเดียวกันที่นี่ เมื่อnavigate(R.id.my_next_destination)ใดที่เรียกว่ากราฟและแบ็กสแต็กจะเปลี่ยนแปลงเกือบจะทันทีในขณะที่ UI ยังไม่อัปเดต ไม่เร็วพอ แต่ก็โอเค หลังจาก back-stack เปลี่ยนระบบนำทางจะรับnavigate(R.id.my_next_destination)สายที่สอง เนื่องจาก back-stack มีการเปลี่ยนแปลงตอนนี้เราจึงทำงานโดยสัมพันธ์กับส่วนบนสุดในสแต็ก ส่วนด้านบนเป็นส่วนที่คุณนำทางไปโดยใช้R.id.my_next_destinationแต่มันไม่ได้มีสถานที่ท่องเที่ยวต่อไปเพิ่มเติมใด ๆ ที่มี R.id.my_next_destinationID ดังนั้นคุณจะได้รับIllegalArgumentExceptionเนื่องจาก ID ที่ส่วนที่ไม่รู้อะไรเลย

ข้อผิดพลาดนี้แน่นอนสามารถพบได้ในวิธีการNavController.javafindDestination


4

ในกรณีของฉันปัญหาเกิดขึ้นเมื่อฉันใช้หนึ่งใน Fragment ของฉันซ้ำในviewpagerส่วนย่อยในฐานะลูกของไฟล์viewpager. viewpagerส่วน (ซึ่งเป็นส่วนที่ผู้ปกครอง) ถูกเพิ่มเข้ามาใน XML นำร่อง แต่การกระทำก็ไม่ได้เพิ่มในviewpagerส่วนของผู้ปกครอง

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

แก้ไขปัญหาโดยการเพิ่มการดำเนินการไปยังส่วนหลักของ Viewpager ดังที่แสดงด้านล่าง:

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

ในวันนี้

def navigationVersion = "2.2.1"

ปัญหายังคงมีอยู่ แนวทางของฉันเกี่ยวกับ 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)
    }
}

4

คุณสามารถตรวจสอบก่อนที่จะนำทางว่า Fragment ที่ขอการนำทางยังคงเป็นปลายทางปัจจุบันหรือไม่โดยนำมาจากส่วนสำคัญนี้

โดยทั่วไปจะตั้งค่าแท็กบนส่วนสำหรับการค้นหาในภายหลัง

/**
 * 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 เป็นเพียง id ที่คุณจะต้องเพิ่มใน ids.xml ของคุณเพื่อให้แน่ใจว่าไม่ซ้ำกัน <item name="tag_navigation_destination_id" type="id" />

ข้อมูลเพิ่มเติมเกี่ยวกับข้อบกพร่องและวิธีการแก้ปัญหาและnavigateSafe(...)วิธีการขยายใน"การแก้ไขปัญหา“ …ไม่รู้จักกับ NavController”


ฉันได้ศึกษาวิธีแก้ไขปัญหานี้แล้วและของคุณเป็นวิธีที่ดีที่สุด ทำให้ฉันเสียใจที่เห็นความรักเพียงเล็กน้อย
ลูกา

1
อาจเป็นประโยชน์ในการสร้างตัวระบุที่ไม่ซ้ำกันแทนNAV_DESTINATION_IDสิ่งนี้stackoverflow.com/a/15021758/1572848
William Reed

ใช่ฉันอัปเดตคำตอบ
แฟรงค์

แท็กมาจากไหนและทำไมจึงต้องใช้ R.idฉันได้ปัญหาที่เกิดขึ้นจริงเป็นรหัสในส่วนทิศทางไม่ตรงกับเหล่านี้จาก
riezebosch

R.id.tag_navigation_destination_idเป็นเพียง id ที่คุณจะต้องเพิ่มใน ids.xml ของคุณเพื่อให้แน่ใจว่าเป็นเอกลักษณ์ <item name="tag_navigation_destination_id" type="id" />
Frank

3

ในกรณีของฉันฉันมีไฟล์กราฟการนำทางหลายไฟล์และฉันพยายามย้ายจากตำแหน่งกราฟการนำทาง 1 ตำแหน่งไปยังปลายทางในกราฟการนำทางอื่น

สำหรับสิ่งนี้เราต้องรวมกราฟการนำทางที่ 2 ไว้ในกราฟที่ 1 เช่นนี้

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

และเพิ่มสิ่งนี้ให้กับการกระทำของคุณ:

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

อยู่ที่ไหน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">

ในกราฟที่สอง

ข้อมูลเพิ่มเติมที่นี่


3

ฉันได้แก้ไขปัญหาเดียวกันโดยใส่เครื่องหมายถูกก่อนนำทางแทนที่จะเป็นรหัสสำเร็จรูปสำหรับการคลิกควบคุมทันที

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

ตามคำตอบนี้

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


2

ในกรณีของข้อผิดพลาดบางประการทำให้เพราะผมมีการกระทำนำทางด้วยSingle TopและClear Taskตัวเลือกเปิดใช้งานหลังจากที่หน้าจอสาด


1
แต่ clearTask เลิกใช้แล้วคุณควรใช้ popUpTo () แทน
Jerry Okafor

@ Po10cio แฟล็กเหล่านั้นไม่จำเป็นฉันเพิ่งลบออกและได้รับการแก้ไขแล้ว
Eury PérezBeltré

2

ฉันได้รับข้อผิดพลาดเดียวกันนี้เนื่องจากฉันใช้ Navigation Drawer และgetSupportFragmentManager().beginTransaction().replace( )ในเวลาเดียวกันในโค้ดของฉัน

ฉันกำจัดข้อผิดพลาดโดยใช้เงื่อนไขนี้ (ทดสอบว่าปลายทาง):

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

ในกรณีของฉันข้อผิดพลาดก่อนหน้านี้เกิดขึ้นเมื่อฉันคลิกที่ตัวเลือกลิ้นชักการนำทาง โดยทั่วไปรหัสด้านบนซ่อนข้อผิดพลาดเนื่องจากในรหัสของฉันบางแห่งฉันใช้การนำทางโดยใช้getSupportFragmentManager().beginTransaction().replace( )เงื่อนไข -

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

ไม่เคยไปถึงเพราะ(Navigation.findNavController(v).getCurrentDestination().getId()มักจะถูกแย่งส่วนของบ้าน คุณต้องใช้Navigation.findNavController(v).navigate(R.id.your_action)หรือฟังก์ชันตัวควบคุมกราฟการนำทางเท่านั้นสำหรับการดำเนินการนำทางทั้งหมดของคุณ


1

ดูเหมือนว่าคุณกำลังเคลียร์งาน แอปอาจมีการตั้งค่าครั้งเดียวหรือชุดหน้าจอการเข้าสู่ระบบ หน้าจอเงื่อนไขเหล่านี้ไม่ควรถือเป็นปลายทางเริ่มต้นของแอปของคุณ

https://developer.android.com/topic/libraries/architecture/navigation/navigation-conditional


1

ฉันพบข้อยกเว้นนี้หลังจากเปลี่ยนชื่อชั้นเรียน ตัวอย่างเช่นผมเคยเรียนที่เรียกว่าFragmentAมี@+is/fragment_aในกราฟนำทางและมีFragmentB @+id/fragment_bแล้วฉันจะลบFragmentAและเปลี่ยนชื่อไปFragmentB FragmentAดังนั้นหลังจากที่โหนดที่FragmentAยังคงอยู่ในกราฟนำทางและandroid:nameของFragmentB's path.to.FragmentAโหนดได้เปลี่ยนชื่อเป็น ฉันมีสองโหนดที่เหมือนกันandroid:nameและแตกต่างกันandroid:idและการกระทำที่ฉันต้องการถูกกำหนดบนโหนดของคลาสที่ถูกลบ


1

มันเกิดขึ้นกับฉันเมื่อฉันกดปุ่มย้อนกลับสองครั้ง ตอนแรกผมตัดและแทนที่KeyListener KeyEvent.KEYCODE_BACKฉันเพิ่มรหัสด้านล่างในฟังก์ชันที่ตั้งชื่อOnResumeสำหรับ Fragment จากนั้นคำถาม / ปัญหานี้จะได้รับการแก้ไข

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

เมื่อมันเกิดขึ้นกับฉันเป็นครั้งที่สองและสถานะก็เหมือนกับครั้งแรกฉันพบว่าฉันอาจจะใช้adsurdฟังก์ชันนี้ ลองวิเคราะห์สถานการณ์เหล่านี้

  1. ประการแรก FragmentA ไปที่ FragmentB จากนั้น FragmentB จะไปที่ FragmentA จากนั้นกดปุ่มย้อนกลับ ... ความผิดพลาดจะปรากฏขึ้น

  2. ประการที่สอง FragmentA ไปที่ FragmentB จากนั้น FragmentB จะไปที่ FragmentC จากนั้น FragmentC จะไปที่ FragmentA จากนั้นกดปุ่มย้อนกลับ ...

ดังนั้นฉันคิดว่าเมื่อกดปุ่มย้อนกลับ FragmentA จะกลับไปที่ FragmentB หรือ FragmentC จากนั้นก็ทำให้การล็อกอินยุ่งเหยิง ในที่สุดฉันก็พบว่าฟังก์ชันที่ตั้งชื่อpopBackStackสามารถใช้สำหรับย้อนกลับมากกว่าการนำทาง

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

จนถึงขณะนี้ปัญหาได้รับการแก้ไขแล้วจริงๆ


1

ดูเหมือนว่าการผสมการควบคุม FragmentManager ของ backstack และการควบคุม Navigation Architecture ของ backstack อาจทำให้เกิดปัญหานี้ได้เช่นกัน

ตัวอย่างเช่นตัวอย่างพื้นฐาน CameraX ดั้งเดิมใช้การนำทางด้านหลังของ fragmentManager ดังต่อไปนี้และดูเหมือนว่ามันไม่ได้โต้ตอบกับการนำทางอย่างถูกต้อง:

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

หากคุณบันทึก 'ปลายทางปัจจุบัน' ด้วยเวอร์ชันนี้ก่อนที่จะย้ายจากแฟรกเมนต์หลัก (ส่วนของกล้องในกรณีนี้) จากนั้นบันทึกอีกครั้งเมื่อคุณกลับไปที่แฟรกเมนต์หลักคุณสามารถดูได้จาก id ในบันทึกที่ id ไม่เหมือนกัน. ในการคาดเดาการนำทางจะอัปเดตเมื่อย้ายไปยังส่วนและ fragmntManager ไม่ได้อัปเดตอีกครั้งเมื่อย้ายกลับ จากบันทึก:

ก่อน : D / CameraXBasic: currentDest ?: androidx.navigation.fragment.FragmentNavigator$Destination@b713195

หลัง : D / CameraXBasic: currentDest ?: androidx.navigation.fragment.FragmentNavigator$Destination@9807d8f

ตัวอย่างพื้นฐานของ CameraX เวอร์ชันอัปเดตใช้การนำทางเพื่อส่งคืนในลักษณะนี้:

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

สิ่งนี้ทำงานได้อย่างถูกต้องและบันทึกจะแสดงรหัสเดียวกันเมื่อกลับไปที่ส่วนหลัก

ก่อน : D / CameraXBasic: currentDest ?: androidx.navigation.fragment.FragmentNavigator$Destination@b713195

หลัง : D / CameraXBasic: currentDest ?: androidx.navigation.fragment.FragmentNavigator$Destination@b713195

ฉันสงสัยว่าคุณธรรมของเรื่องราวอย่างน้อยก็ในเวลานี้คือการผสม Navigation กับการนำทาง fragmentManager อย่างระมัดระวัง


ฟังดูมีความเป็นไปได้เราจะตรวจสอบเพิ่มเติม มีใครสามารถตรวจสอบหรือยืนยันการอ้างสิทธิ์นี้ได้บ้าง
Jerry Okafor

@JerryOkafor - ฉันได้ทดสอบในแอปที่ฉันใช้งานโดยอิงจาก CameraX Sample และตรวจสอบแล้ว แต่จะเป็นการดีที่จะดูว่ามีใครเห็นสิ่งนี้ด้วยหรือไม่ จริงๆแล้วฉันพลาด 'การนำทางย้อนกลับ' ในที่เดียวในแอพเดียวกันดังนั้นเพิ่งแก้ไขอีกครั้งเมื่อเร็ว ๆ นี้
มิก

1

วิธีที่ไร้สาระ แต่ทรงพลังมากคือ: เรียกสิ่งนี้ว่า:

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

เพียงแค่สร้างส่วนขยายนี้:

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

1

อาจมีสาเหตุหลายประการสำหรับปัญหานี้ ในกรณีของฉันฉันใช้โมเดล MVVM และฉันสังเกตเห็นบูลีนสำหรับการนำทางเมื่อบูลีนเป็นจริง -> นำทางอื่น ๆ อย่าทำอะไรเลยและมันก็ใช้งานได้ดี แต่มีข้อผิดพลาดอย่างหนึ่งที่นี่

เมื่อกดปุ่มย้อนกลับจากส่วนปลายทางฉันพบปัญหาเดียวกันและปัญหาคือวัตถุบูลีนเนื่องจากฉันลืมเปลี่ยนค่าบูลีนเป็นเท็จสิ่งนี้สร้างความยุ่งเหยิงฉันเพิ่งสร้างฟังก์ชันใน viewModel เพื่อเปลี่ยนค่าเป็นเท็จและ เรียกมันว่าหลังจาก findNavController ()


1

โดยปกติเมื่อสิ่งนี้เกิดขึ้นกับฉันฉันมีปัญหาที่ Charles Madere อธิบายไว้: เหตุการณ์การนำทางสองเหตุการณ์ที่ทริกเกอร์บน UI เดียวกันเหตุการณ์หนึ่งเปลี่ยน currentDestination และอีกเหตุการณ์หนึ่งล้มเหลวเนื่องจาก currentDestination มีการเปลี่ยนแปลง สิ่งนี้สามารถเกิดขึ้นได้หากคุณแตะสองครั้งหรือคลิกที่สองมุมมองโดยใช้ตัวฟังคลิกที่เรียก findNavController.navigate

ดังนั้นในการแก้ไขปัญหานี้คุณสามารถใช้ if-checks, try-catch หรือหากคุณสนใจก็มี findSafeNavController () ซึ่งจะตรวจสอบให้คุณก่อนที่จะนำทาง นอกจากนี้ยังมีการตรวจสอบผ้าสำลีเพื่อให้แน่ใจว่าคุณจะไม่ลืมปัญหานี้

GitHub

บทความรายละเอียดปัญหา


1

หากคุณคลิกเร็วเกินไปมันจะทำให้เป็นโมฆะและผิดพลาด

เราสามารถใช้ RxBinding lib เพื่อช่วยในเรื่องนี้ คุณสามารถเพิ่มเค้นและระยะเวลาในการคลิกก่อนที่จะเกิดขึ้น

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

เหล่านี้บทความเกี่ยวกับการควบคุมปริมาณบน Android อาจช่วย ไชโย!


1

หากคุณใช้มุมมองรีไซเคิลเพียงแค่เพิ่มคูลดาวน์ click Listener เมื่อคลิกของคุณและในไฟล์รีไซเคิล xml ของคุณ android:splitMotionEvents="false"


1
ดูคำตอบด้านล่างของฉัน
Crazy

1

หลังจากคิดถึงคำแนะนำของ Ian Lake ในหัวข้อ Twitter นี้ฉันได้คิดแนวทางต่อไปนี้ ได้NavControllerWrapperกำหนดไว้ดังนี้:

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

จากนั้นในรหัสการนำทาง:

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

0

เรื่องนี้เกิดขึ้นกับผมว่าปัญหาของฉันถูกฉันถูกคลิก FAB tab item fragmentบน another fragmentผมพยายามที่จะนำทางจากส่วนหนึ่งในรายการที่แท็บ

แต่ตามที่เอียนทะเลสาบในคำตอบนี้เราจะต้องใช้tablayoutและviewpager, ไม่มีการสนับสนุนองค์ประกอบนำทาง ด้วยเหตุนี้จึงไม่มีเส้นทางการนำทางจากแท็บเลย์เอาต์ที่มีแฟรกเมนต์ไปยังแฟรกเมนต์ไอเท็มแท็บ

อดีต:

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

วิธีแก้ไขคือการสร้างเส้นทางจากเค้าโครงแท็บที่มีแฟรกเมนต์ไปยังแฟรกเมนต์ที่ตั้งใจไว้เช่นพา ธ : container fragment -> another fragment

ข้อด้อย:

  • กราฟการนำทางไม่แสดงขั้นตอนของผู้ใช้อย่างถูกต้องอีกต่อไป

0

ในกรณีของฉันฉันได้รับข้อผิดพลาดนั้นเมื่อพยายามนำทางจากเธรดอื่นใน 50% ของกรณี เรียกใช้รหัสบนเธรดหลักช่วย

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

ฉันต้องการเห็นการโหวตเพิ่มเติมเกี่ยวกับเรื่องนี้ฟังดูเป็นไปได้ แต่ฉันไม่สามารถยืนยันได้
Jerry Okafor

0

ในกรณีของฉันสิ่งนี้เกิดขึ้นเมื่อฉันเพิ่ม+ปลายทางในการดำเนินการโดยไม่ได้ตั้งใจและความผิดพลาดจะเกิดขึ้นเมื่อฉันไปที่ส่วนเดียวกันหลาย ๆ ครั้ง

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

วิธีแก้ไขคือการลบออก+จากปลายทางการดำเนินการใช้@id/profileFragmentแทนเท่านั้น@+id/profileFragment

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

0

อัปเดตโซลูชัน @Alex Nuts

หากไม่มีการดำเนินการสำหรับแฟรกเมนต์เฉพาะและต้องการนำทางไปยังแฟรกเมนต์

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

ฉันเขียนส่วนขยายนี้

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

0

ฉันสร้างฟังก์ชันส่วนขยายนี้สำหรับ 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)
        }
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.