当我试图从一个片段导航到另一个片段时,我遇到了新的Android导航架构组件的问题,我得到了这个奇怪的错误:
java.lang.IllegalArgumentException: navigation destination XXX
is unknown to this NavController
其他导航都很好,除了这个。
我使用Fragment的findNavController()函数来访问NavController。
任何帮助都将不胜感激。
当我试图从一个片段导航到另一个片段时,我遇到了新的Android导航架构组件的问题,我得到了这个奇怪的错误:
java.lang.IllegalArgumentException: navigation destination XXX
is unknown to this NavController
其他导航都很好,除了这个。
我使用Fragment的findNavController()函数来访问NavController。
任何帮助都将不胜感激。
当前回答
试试
创建以下扩展函数(或普通函数):
UPDATED(不带反射,可读性更强)
import androidx.fragment.app.Fragment
import androidx.navigation.NavController
import androidx.navigation.NavDirections
import androidx.navigation.fragment.DialogFragmentNavigator
import androidx.navigation.fragment.FragmentNavigator
fun Fragment.safeNavigateFromNavController(directions: NavDirections) {
val navController = findNavController()
when (val destination = navController.currentDestination) {
is FragmentNavigator.Destination -> {
if (javaClass.name == destination.className) {
navController.navigate(directions)
}
}
is DialogFragmentNavigator.Destination -> {
if (javaClass.name == destination.className) {
navController.navigate(directions)
}
}
}
}
老了(沉思)
import androidx.fragment.app.Fragment
import androidx.navigation.NavController
import androidx.navigation.NavDirections
import androidx.navigation.fragment.FragmentNavigator
inline fun <reified T : Fragment> NavController.safeNavigate(directions: NavDirections) {
val destination = this.currentDestination as FragmentNavigator.Destination
if (T::class.java.name == destination.className) {
navigate(directions)
}
}
然后像这样使用你的片段:
val direction = FragmentOneDirections.actionFragmentOneToFragmentTwo()
// new usage
safeNavigateFromNavController(direction)
// old usage
// findNavController().safeNavigate<FragmentOne>(action)
我的问题是
我有一个片段(FragmentOne),它指向另外两个片段(FragmentTwo和FragmentThree)。在一些低级设备中,用户按下重定向到FragmentTwo的按钮,但在用户按下重定向到FragmentThree的按钮几毫秒后。结果是:
致命异常:java.lang.IllegalArgumentException导航 action/destination action_fragmentOne_to_fragmentTwo找不到 来自当前目的地 类= FragmentThree
我的答案是:
我检查当前目的地是否属于当前片段。如果为真,则执行导航操作。
就这些!
其他回答
在我的例子中,如果用户非常非常快地点击同一个视图两次,这个崩溃就会发生。所以你需要实现某种逻辑来防止多次快速点击……这很烦人,但似乎是必要的。
你可以在这里阅读更多关于防止这种情况的内容:Android防止双击按钮
编辑3/19/2019:为了进一步澄清一点,这个崩溃不是仅仅通过“非常非常快地单击同一个视图两次”就可以完全重现的。或者,您可以使用两个手指同时单击两个(或更多)视图,其中每个视图都有自己的导航。当你有一个项目列表时,这尤其容易做到。以上关于多次点击预防的信息将处理这种情况。
编辑4/16/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
}
}
在调用navigate之前检查currentDestination可能会有帮助。
例如,如果您在导航图fragmentA和fragmentB上有两个片段目的地,并且从fragmentA到fragmentB只有一个动作。当你已经在fragmentB上时,调用navigate(R.id.action_fragmentA_to_fragmentB)将导致IllegalArgumentException。因此,在导航之前,你应该总是检查currentDestination。
if (navController.currentDestination?.id == R.id.fragmentA) {
navController.navigate(R.id.action_fragmentA_to_fragmentB)
}
您可以在导航之前检查请求导航的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" />
更多关于bug和解决方案的信息,以及navigateSafe(…)扩展方法在“修复可怕的”…是未知的NavController”
当我按了两次后退键时,我想到了这个问题。首先,我拦截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
}
}
当我第二次遇到这种情况时,它的状态与第一次相同,我发现我可能会使用add函数。让我们来分析一下这些情况。
首先,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
)
到目前为止,问题已经真正解决了。
I caught this exception after some renames of classes. For example: I had classes called FragmentA with @+is/fragment_a in navigation graph and FragmentB with @+id/fragment_b. Then I deleted FragmentA and renamed FragmentB to FragmentA. So after that node of FragmentA still stayed in navigation graph, and android:name of FragmentB's node was renamed path.to.FragmentA. I had two nodes with the same android:name and different android:id, and the action I needed were defined on node of removed class.