web-dev-qa-db-ja.com

java.lang.IllegalArgumentException:ナビゲーション先xxxはこのNavControllerに不明です

あるフラグメントから別のフラグメントからナビゲートしようとすると、新しいAndroidナビゲーションアーキテクチャコンポーネントに問題があります。この奇妙なエラーが発生します。

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

この特定のナビゲーションを除き、他のナビゲーションはすべて正常に機能します。

私が使う:

findNavContoller()

navControlerにアクセスするためのフラグメントの拡張機能。

任意の助けをいただければ幸いです。

39
Po10cio

私の場合、ユーザーが同じビューを非常にすばやく2回クリックすると、このクラッシュが発生します。したがって、複数のクイッククリックを防ぐために何らかのロジックを実装する必要があります。これは非常に迷惑ですが、必要なようです。

これを防止する方法について詳しくは、こちらをご覧ください。 Androidのボタンのダブルクリック防止

Edit 3/19/2019:もう少し明確にするために、このクラッシュは「同じビューを非常にすばやく2回クリックする」だけでは再現できません。または、2本の指を使用して、同時に2つ(またはそれ以上)のビューをクリックすることもできます。各ビューでは、実行する独自のナビゲーションがあります。これは特にアイテムのリストがある場合に簡単です。複数のクリックの防止に関する上記の情報がこのケースを処理します。

32
Charles Madere

ナビゲートを呼び出す前に、currentDestinationを確認してください。

たとえば、ナビゲーショングラフに2つのフラグメントの宛先fragmentAおよびfragmentBがあり、fragmentAからfragmentBまでのアクションが1つしかない場合。 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)
}
20
theJian

Navigation Controllerの現在の宛先で要求されたアクションを確認できます

fun NavController.navigateSafe(
        @IdRes resId: Int,
        args: Bundle? = null,
        navOptions: NavOptions? = null,
        navExtras: Navigator.Extras? = null
) {
    val action = currentDestination?.getAction(resId)
    if (action != null) navigate(resId, args, navOptions, navExtras)
}
14
Alex Nuts

私の場合、ナビゲートするためにカスタムの戻るボタンを使用していました。次のコードの代わりにonBackPressed()を呼び出しました

findNavController(R.id.navigation_Host_fragment).navigateUp()

これにより、IllegalArgumentExceptionが発生しました。代わりにnavigateUp()メソッドを使用するように変更した後、再度クラッシュすることはありませんでした。

4
Neil

また、フラグメントAにフラグメントBのViewPagerがあり、BからCに移動しようとした場合にも発生する可能性があります。

ViewPagerではフラグメントはAの宛先ではないため、グラフはあなたがBにいることを知りません。

ソリューションは、BのADirectionsを使用してCにナビゲートすることです。

3
AntPachon

クラッシュを防ぐために私がしたことは次のとおりです。

BaseFragmentがあるので、fundestinationによって認識されるように、このcurrentDestinationを追加しました。

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

SafeArgs プラグインを使用していることに注意してください。

1
Douglas Kazumi

私の場合、スプラッシュ画面の後にSingle TopオプションとClear Taskオプションを有効にしたナビゲーションアクションがあるため、バグが発生しました。

1

タスクをクリアしているようです。アプリには、1回限りのセットアップまたは一連のログイン画面があります。これらの条件付き画面は、アプリの開始先と見なすべきではありません。

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

1
Patrick

TL; DRnavigate呼び出しをtry-catch(簡単な方法)でラップするか、navigateの呼び出しが短時間で1つだけになるようにします。この問題は消えないでしょう。アプリに大きなコードスニペットをコピーして、試してください。

こんにちは。上記のいくつかの有用な回答に基づいて、拡張可能なソリューションを共有したいと思います。

私のアプリケーションでこのクラッシュを引き起こしたコードは次のとおりです。

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

バグを簡単に再現する方法は、新しい画面へのナビゲーションで各アイテムのクリックが解決するアイテムのリストを複数の指でタップすることです(基本的には、メモした人と同じ-非常に短い時間で2回以上クリックします) )。きがついた:

  1. 最初のnavigate呼び出しは常に正常に機能します。
  2. 2番目およびnavigateメソッドのその他すべての呼び出しは、IllegalArgumentExceptionで解決されます。

私の観点からすると、この状況は非常に頻繁に発生する可能性があります。コードの繰り返しは悪い習慣であり、次の解決策について考えた影響の1つのポイントがあることは常に良いことです。

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

}

したがって、上記のコードはこれから1行だけ変更されます。

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

これに:

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

それは少し短くさえなりました。コードは、クラッシュが発生した正確な場所でテストされました。もう経験しなかったので、他のナビゲーションにも同じソリューションを使用して、同じ間違いをさらに回避します。

どんな考えでも大歓迎です!

クラッシュの正確な原因

ここでは、メソッドNavigation.findNavControllerを使用するときに、同じナビゲーショングラフ、Navigation Controller、およびバックスタックを使用することに注意してください。

ここでは常に同じコントローラーとグラフを取得します。 navigate(R.id.my_next_destination)がグラフと呼ばれ、バックスタックが変更された場合ほぼ瞬時に UIがまだ更新されていません。十分に高速ではありませんが、それでも大丈夫です。バックスタックが変更された後、ナビゲーションシステムは2番目のnavigate(R.id.my_next_destination)呼び出しを受け取ります。バックスタックが変更されたため、スタックの最上位のフラグメントを基準にして動作するようになりました。最上部のフラグメントは、R.id.my_next_destinationを使用してナビゲートするフラグメントですが、ID R.id.my_next_destinationの次の宛先は含まれていません。したがって、フラグメントが何も知らないIDのためにIllegalArgumentExceptionを取得します。

この正確なエラーは、NavController.JavaメソッドfindDestinationにあります。

0
Jenea Vranceanu

これは私に起こりました。私の問題は、tab item fragmentのFABをクリックしていたことです。タブ項目フラグメントの1つからanother fragmentに移動しようとしていました。

Ian Lake in この回答 によれば、tablayoutviewpagerを使用する必要があります、ナビゲーションコンポーネントなしsupport。このため、フラグメントを含むtablayoutからタブ項目フラグメントへのナビゲーションパスはありません。

例:

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

解決策は、フラグメントを含むタブレイアウトから目的のフラグメントexへのパスを作成することでした:パス:container fragment -> another fragment

欠点:

  • ナビゲーショングラフは、ユーザーフローを正確に表していません。
0
user158

クラスの名前を変更した後、この例外をキャッチしました。たとえば、ナビゲーショングラフでFragmentAと呼ばれるクラスと@+is/fragment_a、および@+id/fragment_bと呼ばれるFragmentBというクラスがありました。次に、FragmentAを削除し、FragmentBの名前をFragmentAに変更しました。そのため、FragmentAのノードはナビゲーショングラフに残り、FragmentBのノードのAndroid:namepath.to.FragmentAに名前が変更されました。同じAndroid:nameと異なるAndroid:idの2つのノードがあり、必要なアクションは削除されたクラスのノードで定義されました。

0
VasyaFromRussia

戻るボタンを2回押すと発生します。最初に、KeyListenerをインターセプトし、KeyEvent.KEYCODE_BACKをオーバーライドします。フラグメントのOnResumeという名前の関数に以下のコードを追加すると、この質問/問題は解決しました。

  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
                        )

これまでのところ、問題は本当に解決されています。

0
唐德坤