IllegalArgumentException : 탐색 대상 xxx가이 NavController에 알려지지 않았습니다.


139

한 Fragment에서 다른 Fragment로 이동하려고 할 때 새로운 Android 탐색 아키텍처 구성 요소에 문제 가 있습니다.이 이상한 오류가 발생합니다.

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

이 특정 탐색을 제외하고 다른 모든 탐색은 잘 작동합니다.

findNavController()Fragment의 기능을 사용 하여 NavController.

도움을 주시면 감사하겠습니다.


더 나은 이해를 위해 몇 가지 코드를 제공하십시오.
— Alex

12
이것은 나에게도 일어나고 있습니다.
— Eury Pérez Beltré

지금까지이 버그의 발생률은 라이브러리의 최신 릴리스에서 감소되었지만 라이브러리가 아직 잘 문서화되지 않은 것 같습니다.
— Jerry Okafor

답변:


76

제 경우에는 사용자가 동일한 뷰를 매우 빠르게 두 번 클릭하면이 충돌이 발생합니다. 따라서 여러 번의 빠른 클릭을 방지하기 위해 일종의 논리를 구현해야합니다. 매우 성가 시지만 필요한 것 같습니다.

이를 방지하는 방법에 대한 자세한 내용은 여기에서 확인할 수 있습니다. Android에서 더블 클릭을 방지하는 버튼

2019 년 3 월 19 일 편집 : 좀 더 명확히하기 위해이 충돌은 "동일한 뷰를 매우 빠르게 두 번 클릭"하는 것만으로 만 재현 할 수있는 것은 아닙니다. 또는 두 손가락을 사용하고 동시에 두 개 (또는 그 이상의)보기를 클릭 할 수 있습니다. 여기서 각보기에는 수행 할 고유 한 탐색이 있습니다. 이것은 항목 목록이있을 때 특히 쉽게 수행 할 수 있습니다. 다중 클릭 방지에 대한 위의 정보가이 경우를 처리합니다.

2020 년 4 월 16 일 편집 : 위의 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 개의 뷰를 클릭하는 것에 대한 편집! 그것이 제게 핵심이며 문제를 쉽게 재현 할 수 있도록 도와주었습니다. 그 정보로 훌륭한 업데이트.
— Richard Le Mesurier 2019

디버그 단계에서 앱이 계속 실행되기를 기다리는 동안 클릭이 발생했습니다. 는 IDE에 연속으로 두 개의 연속 클릭의 또 다른 사례처럼 보인다
— 마르코

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

84
도서관은 우리에게이 검사를 강요해서는 안됩니다. 정말 말도 안됩니다.
— 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의 작업 목록 외부에 정의 된 작업에는 작동하지 않습니다 . 전역 작업이 정의되어 있고 해당 작업을 사용하여 탐색한다고 가정합니다. 작업이 currentDestination의 <action> 목록에 정의되어 있지 않기 때문에 실패합니다. 같은 수표를 추가하면 currentDestination?.getAction(resId) != null || currentDestination?.id != resId문제가 해결되지만 모든 경우에 적용되지 않을 수도 있습니다.
— wchristiansen

@wchristiansen, 메모 감사합니다. 나는 세계 행동의 사용과 코드를 업데이 트했습니다
— 알렉스 너트

@AlexNuts 좋은 대답. 제거 할 수 있다고 생각합니다 ?: graph.getAction(resId)-> currentDestination?.getAction(resId)전역 또는 비 전역 작업 모두에 대한 작업을 반환합니다 (테스트했습니다). 오히려 전달> - 당신이 안전 인수의 사용 한 경우 또한, 더 나은 것 navDirections: NavDirections보다 더 resId와 args별도.
— Wess

@AlexNuts 참고이 솔루션은 현재 대상과 동일한 대상으로의 탐색을 지원하지 않습니다. 번들 Y가있는 대상 X에서 번들 Z가있는 대상 X로 이동하는 Iow는 불가능합니다.
— Wess

18

또한 Fragment B의 ViewPager가있는 Fragment A가 있고 B에서 C로 이동하려고 할 때 발생할 수 있습니다.

ViewPager에서 조각은 A의 대상이 아니기 때문에 그래프는 사용자가 B에 있다는 것을 알 수 없습니다.

해결책은 B에서 ADirections를 사용하여 C로 이동하는 것입니다.


이 경우 충돌은 매번 발생하는 것이 아니라 드물게 발생합니다. 그것을 해결하는 방법?
— Srikar Reddy

당신은 navGraph 내부 전역 조치를 추가하고 이동하는 데 사용할 수 있습니다
— 아브라함 매튜

1
B는 정확한 부모를 알 필요가 없기 때문에 ADirections와 같은 인터페이스를 통해 사용하는 것이 더 좋을 것 (parentFragment as? XActionListener)?.Xaction()입니다. 도움이된다면이 함수를 지역 변수로 유지할 수 있습니다
— hmac

나는이 같은 문제가 당신이를 설명하는 샘플 코드를 공유하시기 바랍니다 수 있습니다
— Ikhiloya Imokhai을

누구든지 샘플 코드를 plz 할 수 있습니다. 나는 같은 문제에 갇혀 있습니다. 조각과 tabfragment가 있습니다
— 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
미친 것 같다는 데 동의합니다. 안드로이드 내비게이션 아키텍처 구성 요소에서 내가 만난 많은 것들이 약간 미쳤습니다. 너무 엄격하게 IMO로 설정되었습니다. 너무 많은 골칫거리를 만들기 때문에 우리 프로젝트를 직접 구현하는 것에 대해 생각하고 있습니다
— Neil

나를 위해 작동하지 않습니다 ... 여전히 같은 오류가 발생합니다.
— Otziii

5

TL; DRnavigate 통화를 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);

심지어 조금 더 짧아졌습니다. 코드는 충돌이 발생한 정확한 위치에서 테스트되었습니다. 더 이상 경험하지 않았으며 동일한 실수를 더 방지하기 위해 다른 탐색에 동일한 솔루션을 사용할 것입니다.

어떤 생각이라도 환영합니다!

충돌의 정확한 원인

여기서는 method를 사용할 때 동일한 탐색 그래프, 탐색 컨트롤러 및 백 스택으로 작업 Navigation.findNavController합니다.

여기에서는 항상 동일한 컨트롤러와 그래프를 얻습니다. navigate(R.id.my_next_destination)를 호출 하면 UI가 아직 업데이트되지 않은 동안 거의 즉시 백 스택이 변경 됩니다. 충분히 빠르지는 않지만 괜찮습니다. 백 스택이 변경된 후 내비게이션 시스템은 두 번째 navigate(R.id.my_next_destination)호출을 받습니다 . 백 스택이 변경되었으므로 이제 스택의 최상위 조각을 기준으로 작동합니다. 맨 위 조각은를 사용하여 탐색하는 조각 R.id.my_next_destination이지만 ID가있는 다음 대상은 포함하지 않습니다 R.id.my_next_destination. 따라서 IllegalArgumentException조각이 아무것도 모르는 ID 때문에 얻을 수 있습니다.

이 정확한 오류는 NavController.javamethod 에서 찾을 수 있습니다 findDestination.


4

제 경우에는 프래그먼트 내부의 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">

아래와 같이 상위 뷰 페이지 조각에 작업을 추가하여 문제를 해결했습니다.

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

탐색을 요청하는 조각 이이 요점에서 가져온 현재 대상인지 탐색하기 전에 확인할 수 있습니다 .

기본적으로 나중에 조회 할 수 있도록 조각에 태그를 설정합니다.

/**
 * 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 고유한지 확인하기 위해 ids.xml에 추가해야하는 ID입니다. <item name="tag_navigation_destination_id" type="id" />

버그 및 솔루션에 대한 자세한 정보, "두려운"…이 NavController에 알려지지 않은 문제 해결 "의navigateSafe(...) 확장 방법


나는이 문제에 대한 몇 가지 다른 해결책을 연구했으며, 당신의 것이 가장 훌륭합니다. 그것에 대한 너무 작은 사랑을보고 슬프게합니다
— Luke

1
NAV_DESTINATION_ID다음과 같은 대신 고유 식별자를 만드는 것이 유용 할 수 있습니다. stackoverflow.com/a/15021758/1572848
— William Reed

예, 답변을 업데이트했습니다
— Frank

태그의 출처는 어디이며 왜 필요한가요? 탐색 구성 요소의 실제 ID가 .NET과 일치하지 않는 문제가 있습니다 R.id.
— riezebosch

R.id.tag_navigation_destination_id고유한지 확인하기 위해 ids.xml에 추가해야하는 ID 일뿐입니다. <item name="tag_navigation_destination_id" type="id" />
— Frank

3

제 경우에는 여러 탐색 그래프 파일이 있었고 한 탐색 그래프 위치에서 다른 탐색 그래프의 대상으로 이동하려고했습니다.

이를 위해 다음과 같이 첫 번째 탐색 그래프에 두 번째 탐색 그래프를 포함해야합니다.

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

두 번째 그래프에서.

여기에 더 많은 정보


2

제 경우에는 스플래시 화면 후에 Single Top및 Clear Task옵션을 사용 하여 탐색 작업을 수행했기 때문에 버그가 발생했습니다 .


1
그러나 clearTask는 더 이상 사용되지 않으므로 대신 popUpTo ()를 사용해야합니다.
— Jerry Okafor

@ Po10cio 그 플래그 중 어느 것도 필요하지 않았으며 방금 제거했으며 수정되었습니다.
— Eury Pérez Beltré

2

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( )The condition을 사용하여 탐색을 사용했기 때문입니다.

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

(Navigation.findNavController(v).getCurrentDestination().getId()항상 집 조각에 poiting 했기 때문에 도달하지 못했습니다 . Navigation.findNavController(v).navigate(R.id.your_action)모든 탐색 작업에 대해 그래프 컨트롤러 기능 만 사용 하거나 탐색 해야합니다 .


2

즉시 클릭 제어를위한 상용구 코드 대신 탐색 전에 확인을 넣어 동일한 문제를 해결했습니다.

 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



1

클래스 이름을 변경 한 후이 예외가 발생했습니다. 예를 들면 : 나는라는 클래스를 가지고 FragmentA와 @+is/fragment_a탐색 그래프와 FragmentB함께를 @+id/fragment_b. 그럼 삭제 FragmentA하고 이름 FragmentB에 FragmentA. 그래서의 노드 후 FragmentA여전히 탐색 그래프에 머물렀고, 및 android:name의 FragmentB의 노드 이름이 바뀌 었습니다 path.to.FragmentA. 나는 같고 android:name다른 두 개의 노드를 가지고 있었고 android:id필요한 동작은 제거 된 클래스의 노드에 정의되었습니다.


1

뒤로 버튼을 두 번 누르면 나에게 발생합니다. 첫째, 절편에서 KeyListener와 재정 KeyEvent.KEYCODE_BACK. OnResumeFragment의 이름이 지정된 함수에 아래 코드를 추가하면 이 질문 / 문제가 해결됩니다.

  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 컨트롤과 백 스택의 탐색 아키텍처 컨트롤을 혼합하면이 문제가 발생할 수도 있습니다.

예를 들어 원래 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 기본 샘플의 업데이트 된 버전은 Navigation을 사용하여 다음과 같이 반환합니다.

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

이것은 올바르게 작동하며 로그는 기본 조각으로 돌아 왔을 때 동일한 ID를 표시합니다.

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

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

적어도 현재로서는 이야기의 교훈이 Navigation과 fragmentManager 탐색을 매우 신중하게 혼합하는 것이라고 생각합니다.


그럴듯하게 들리면 더 자세히 조사하겠습니다. 누구든지이 주장을 확인하거나 입증 할 수 있었습니까?
— Jerry Okafor

@JerryOkafor-CameraX 샘플을 기반으로 작업 중이던 앱에서 테스트하고 확인했지만 다른 사람도 이것을 보았는지 확인하는 것이 좋습니다. 실제로 동일한 앱의 한 위치에서 '뒤로 탐색'을 놓쳤으므로 최근에도 다시 수정했습니다.
— Mick

1

말도 안되는 방법이지만 매우 강력한 방법은 다음과 같습니다.

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

이 확장 만들기 :

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

1

이 문제에는 여러 가지 이유가있을 수 있습니다. 제 경우에는 MVVM 모델을 사용하고 있었고 부울이 true 일 때 탐색을 위해 부울을 관찰했습니다-> 탐색하지 않으면 아무것도하지 않고 잘 작동했지만 여기에 한 가지 실수가 있습니다.

대상 조각에서 뒤로 버튼을 눌렀을 때 동일한 문제가 발생했습니다. 부울 값을 false로 변경하는 것을 잊었으므로 문제는 부울 객체였습니다. findNavController () 바로 다음에 호출했습니다.


1

일반적으로 이런 일이 발생하면 Charles Madere가 설명한 문제가 있습니다. 동일한 UI에서 두 개의 탐색 이벤트가 트리거되었습니다. 하나는 currentDestination을 변경하고 다른 하나는 currentDestination이 변경되어 실패했습니다. 두 번 탭하거나 findNavController.navigate를 호출하는 클릭 리스너로 두 개의보기를 클릭하면 이런 일이 발생할 수 있습니다.

따라서이 문제를 해결하려면 if-checks, try-catch를 사용하거나 관심이있는 경우 탐색하기 전에이를 확인하는 findSafeNavController ()를 사용할 수 있습니다. 또한이 문제를 잊지 않도록 보풀 검사 기능이 있습니다.

GitHub

문제를 자세히 설명하는 기사


1

너무 빨리 클릭하면 null 및 충돌이 발생합니다.

이를 위해 RxBinding lib를 사용할 수 있습니다. 클릭이 발생하기 전에 스로틀과 기간을 추가 할 수 있습니다.

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

Android의 제한에 대한 이 문서 가 도움이 될 수 있습니다. 건배!


1

recyclerview를 사용하는 경우 클릭에 클릭 리스너 쿨 다운을 추가하고 recyclerview xml 파일 사용 android:splitMotionEvents="false"


1
내 아래 답변을보세요
— Crazy

1

이 트위터 스레드 에서 Ian Lake의 조언을 생각한 후 다음과 같은 접근 방식을 생각해 냈습니다. 데 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, 더 탐색 구성 요소 지원 . 이 때문에 조각을 포함하는 tablayout에서 탭 항목 조각으로의 탐색 경로가 없습니다.

전의:

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

해결책은 조각을 포함하는 탭 레이아웃에서 의도 한 조각으로의 경로를 만드는 것입니다. 예 : path : container fragment -> another fragment

불리:

  • 탐색 그래프가 더 이상 사용자 흐름을 정확하게 나타내지 않습니다.

0

제 경우에는 50 %의 경우 다른 스레드에서 탐색하려고 할 때 오류가 발생했습니다. 메인 스레드에서 코드 실행

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

나는 이것에 대한 더 많은 표를보고 싶습니다. 그럴듯하게 들리지만 그것을 확인할 수 없습니다.
— Jerry Okafor

0

제 경우에 이것은 실수로 +in action 대상을 추가했을 때 발생 했으며 충돌은 동일한 조각에 여러 번 갔을 때만 발생했습니다.

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