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

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

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

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

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

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



답변

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

이를 방지하는 방법에 대한 자세한 내용은 여기에서 확인할 수 있습니다. 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
    }
}


답변

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


답변

내비게이션 컨트롤러의 현재 목적지에서 요청 된 작업을 확인할 수 있습니다.

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


답변

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

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

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


답변

충돌을 방지하기 위해 내가 한 일은 다음과 같습니다.

나는 BaseFragment를 가지고 있으며, 거기에 다음 fun을 통해 알 수 있도록 이것을 추가 destination했습니다 currentDestination.

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

내가 SafeArgs 플러그인을 사용하고 있다는 점은 주목할 가치가 있습니다.


답변

제 경우에는 위로 이동하기 위해 맞춤 뒤로 버튼을 사용했습니다. onBackPressed()다음 코드 대신 전화 했습니다.

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

이로 인해 IllegalArgumentException발생했습니다. navigateUp()대신 방법 을 사용하도록 변경 한 후 다시 충돌이 발생하지 않았습니다.


답변

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.