あるフラグメントから別のフラグメントからナビゲートしようとすると、新しいAndroidナビゲーションアーキテクチャコンポーネントに問題があります。この奇妙なエラーが発生します。
Java.lang.IllegalArgumentException: navigation destination XXX
is unknown to this NavController
この特定のナビゲーションを除き、他のナビゲーションはすべて正常に機能します。
私が使う:
findNavContoller()
navControlerにアクセスするためのフラグメントの拡張機能。
任意の助けをいただければ幸いです。
私の場合、ユーザーが同じビューを非常にすばやく2回クリックすると、このクラッシュが発生します。したがって、複数のクイッククリックを防ぐために何らかのロジックを実装する必要があります。これは非常に迷惑ですが、必要なようです。
これを防止する方法について詳しくは、こちらをご覧ください。 Androidのボタンのダブルクリック防止
Edit 3/19/2019:もう少し明確にするために、このクラッシュは「同じビューを非常にすばやく2回クリックする」だけでは再現できません。または、2本の指を使用して、同時に2つ(またはそれ以上)のビューをクリックすることもできます。各ビューでは、実行する独自のナビゲーションがあります。これは特にアイテムのリストがある場合に簡単です。複数のクリックの防止に関する上記の情報がこのケースを処理します。
ナビゲートを呼び出す前に、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)
}
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)
}
私の場合、ナビゲートするためにカスタムの戻るボタンを使用していました。次のコードの代わりにonBackPressed()
を呼び出しました
findNavController(R.id.navigation_Host_fragment).navigateUp()
これにより、IllegalArgumentException
が発生しました。代わりにnavigateUp()
メソッドを使用するように変更した後、再度クラッシュすることはありませんでした。
また、フラグメントAにフラグメントBのViewPagerがあり、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 プラグインを使用していることに注意してください。
私の場合、スプラッシュ画面の後にSingle Top
オプションとClear Task
オプションを有効にしたナビゲーションアクションがあるため、バグが発生しました。
タスクをクリアしているようです。アプリには、1回限りのセットアップまたは一連のログイン画面があります。これらの条件付き画面は、アプリの開始先と見なすべきではありません。
https://developer.Android.com/topic/libraries/architecture/navigation/navigation-conditional
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回以上クリックします) )。きがついた:
navigate
呼び出しは常に正常に機能します。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
にあります。
これは私に起こりました。私の問題は、tab item fragment
のFABをクリックしていたことです。タブ項目フラグメントの1つからanother fragment
に移動しようとしていました。
Ian Lake in この回答 によれば、tablayout
とviewpager
を使用する必要があります、ナビゲーションコンポーネントなしsupport。このため、フラグメントを含むtablayoutからタブ項目フラグメントへのナビゲーションパスはありません。
例:
containing fragment -> tab layout fragment -> tab item fragment -> another fragment
解決策は、フラグメントを含むタブレイアウトから目的のフラグメントexへのパスを作成することでした:パス:container fragment -> another fragment
欠点:
クラスの名前を変更した後、この例外をキャッチしました。たとえば、ナビゲーショングラフでFragmentA
と呼ばれるクラスと@+is/fragment_a
、および@+id/fragment_b
と呼ばれるFragmentB
というクラスがありました。次に、FragmentA
を削除し、FragmentB
の名前をFragmentA
に変更しました。そのため、FragmentA
のノードはナビゲーショングラフに残り、FragmentB
のノードのAndroid:name
はpath.to.FragmentA
に名前が変更されました。同じAndroid:name
と異なるAndroid:id
の2つのノードがあり、必要なアクションは削除されたクラスのノードで定義されました。
戻るボタンを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
関数を使用することがわかります。これらの状況を分析しましょう。
まず、FragmentAがFragmentBに移動し、FragmentBがFragmentAに移動してから、戻るボタンを押すと、クラッシュが表示されます。
次に、FragmentAがFragmentBに移動し、FragmentBがFragmentCに移動し、FragmentCがFragmentAに移動し、戻るボタンを押すと、クラッシュが表示されます。
だから、戻るボタンを押すと、FragmentAはFragmentBまたはFragmentCに戻り、それがログイン混乱を引き起こすと思います。最後に、popBackStack
という名前の関数は、ナビゲートするのではなく戻るために使用できることがわかりました。
NavHostFragment.findNavController(this@TeacherCloudResourcesFragment).
.popBackStack(
R.id.teacher_prepare_lesson_main_fragment,false
)
これまでのところ、問題は本当に解決されています。