Kotlin Navigation导航异常:第二次点击按钮崩溃排查
导航崩溃问题:第二次点击按钮触发IllegalArgumentException
点击id为stavzac的ImageButton,第一次能正常从StaveniFragment导航到StaveniZacatecnikFragment,但第二次点击按钮时应用直接崩溃,Logcat关键报错:
java.lang.IllegalArgumentException: Navigation action/destination com.example.ludva2alpha:id/action_staveniFragment_to_staveniZacatecnikFragment cannot be found from the current destination Destination(com.example.ludva2alpha:id/staveniZacatecnikFragment) label=fragment_staveni_zacatecnik class=com.example.ludva2alpha.StaveniZacatecnikFragment
相关代码
nav_graph.xml
<?xml version="1.0" encoding="utf-8"?> <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/nav_graph" app:startDestination="@id/staveniFragment"> <fragment android:id="@+id/staveniFragment" android:name="com.example.ludva2alpha.StaveniFragment" android:label="fragment_staveni" tools:layout="@layout/fragment_staveni" > <action android:id="@+id/action_staveniFragment_to_staveniZacatecnikFragment" app:destination="@id/staveniZacatecnikFragment" /> </fragment> <fragment android:id="@+id/staveniZacatecnikFragment" android:name="com.example.ludva2alpha.StaveniZacatecnikFragment" android:label="fragment_staveni_zacatecnik" tools:layout="@layout/fragment_staveni_zacatecnik" /> </navigation>
StaveniFragment.kt
package com.example.ludva2alpha import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageButton import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController import com.example.ludva2alpha.databinding.FragmentStaveniBinding class StaveniFragment : Fragment() { private lateinit var binding: FragmentStaveniBinding override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { binding = FragmentStaveniBinding.inflate(inflater, container, false) val view = binding.root val stavzacButton: ImageButton = binding.stavzac stavzacButton.setOnClickListener { val navController = findNavController() navController.navigate(R.id.action_staveniFragment_to_staveniZacatecnikFragment) } return view } }
MainActivity.kt
package com.example.ludva2alpha import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.fragment.app.Fragment import androidx.navigation.NavController import androidx.navigation.findNavController import androidx.viewpager2.widget.ViewPager2 import com.google.android.material.bottomnavigation.BottomNavigationView class MainActivity : AppCompatActivity() { private lateinit var bottomNavigationView: BottomNavigationView var currentPage = 0 private fun updateBottomNavigationBarIcons() { when (currentPage) { 0 -> bottomNavigationView.selectedItemId = R.id.bottom_knihovna 1 -> bottomNavigationView.selectedItemId = R.id.bottom_staveni 2 -> bottomNavigationView.selectedItemId = R.id.bottom_domov 3 -> bottomNavigationView.selectedItemId = R.id.bottom_programovani 4 -> bottomNavigationView.selectedItemId = R.id.bottom_nastaveni } } private lateinit var navController: NavController override fun onSupportNavigateUp(): Boolean { navController = findNavController(R.id.navHostFragmentContainerView) return navController.navigateUp() || super.onSupportNavigateUp() } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Thread.sleep(3000) installSplashScreen() setContentView(R.layout.activity_main) val viewPager: ViewPager2 = findViewById(R.id.view_pager) val fragmets: ArrayList<Fragment> = arrayListOf( KnihovnaFragment(), StaveniFragment(), DomovFragment(), ProgramovaniFragment(), NastaveniFragment() ) viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) { currentPage = position updateBottomNavigationBarIcons() } }) val adapter = ViewPagerAdapter(fragmets, this) viewPager.adapter = adapter bottomNavigationView = findViewById(R.id.bottom_navigation) bottomNavigationView.setOnItemSelectedListener { menuItem -> val currentItem = when (menuItem.itemId) { R.id.bottom_knihovna -> 0 R.id.bottom_staveni -> 1 R.id.bottom_domov -> 2 R.id.bottom_programovani -> 3 R.id.bottom_nastaveni -> 4 else -> -1 } if (currentItem >= 0) { viewPager.currentItem = currentItem true } else { false } } bottomNavigationView.selectedItemId = R.id.bottom_domov } }
StaveniZacatecnikFragment.kt
package com.example.ludva2alpha import android.os.Bundle import android.view.View import androidx.fragment.app.Fragment class StaveniZacatecnikFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // 注释掉的导航代码 /* val imageButton = view.findViewById<ImageButton>(R.id.stavzac) imageButton.setOnClickListener { findNavController().navigate(StaveniFragmentDirections.actionStaveniFragmentToStaveniZacatecnikFragment()) }*/ } }
问题原因
你同时混用了ViewPager2和Navigation Component两种Fragment管理方式,导致导航栈和ViewPager的Fragment实例不一致:
- 第一次点击按钮,从StaveniFragment导航到StaveniZacatecnikFragment,此时Navigation的栈顶是StaveniZacatecnikFragment
- 当你通过底部导航切回Staveni页面时,ViewPager2显示的是之前创建的StaveniFragment旧实例,而Navigation的当前目的地还是StaveniZacatecnikFragment
- 再次点击按钮时,旧的StaveniFragment实例尝试执行
action_staveniFragment_to_staveniZacatecnikFragment动作,但当前导航目的地是StaveniZacatecnikFragment,这个动作不存在于该Fragment的导航配置中,所以抛出异常
解决方案
方案一:统一使用Navigation Component管理底部导航(推荐)
完全移除ViewPager2,改用NavigationUI绑定底部导航和NavController,让Navigation统一管理所有Fragment的生命周期和导航栈:
- 修改
nav_graph.xml,添加所有底部导航对应的Fragment,并设置正确的startDestination:
<?xml version="1.0" encoding="utf-8"?> <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/nav_graph" app:startDestination="@id/domovFragment"> <fragment android:id="@+id/knihovnaFragment" android:name="com.example.ludva2alpha.KnihovnaFragment" android:label="fragment_knihovna" tools:layout="@layout/fragment_knihovna" /> <fragment android:id="@+id/staveniFragment" android:name="com.example.ludva2alpha.StaveniFragment" android:label="fragment_staveni" tools:layout="@layout/fragment_staveni" > <action android:id="@+id/action_staveniFragment_to_staveniZacatecnikFragment" app:destination="@id/staveniZacatecnikFragment" /> </fragment> <fragment android:id="@+id/domovFragment" android:name="com.example.ludva2alpha.DomovFragment" android:label="fragment_domov" tools:layout="@layout/fragment_domov" /> <fragment android:id="@+id/programovaniFragment" android:name="com.example.ludva2alpha.ProgramovaniFragment" android:label="fragment_programovani" tools:layout="@layout/fragment_programovani" /> <fragment android:id="@+id/nastaveniFragment" android:name="com.example.ludva2alpha.NastaveniFragment" android:label="fragment_nastaveni" tools:layout="@layout/fragment_nastaveni" /> <fragment android:id="@+id/staveniZacatecnikFragment" android:name="com.example.ludva2alpha.StaveniZacatecnikFragment" android:label="fragment_staveni_zacatecnik" tools:layout="@layout/fragment_staveni_zacatecnik" /> </navigation>
- 修改
MainActivity.kt,移除ViewPager2相关代码,改用NavigationUI绑定底部导航:
package com.example.ludva2alpha import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.navigation.NavController import androidx.navigation.findNavController import androidx.navigation.ui.setupWithNavController import com.google.android.material.bottomnavigation.BottomNavigationView class MainActivity : AppCompatActivity() { private lateinit var navController: NavController override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Thread.sleep(3000) installSplashScreen() setContentView(R.layout.activity_main) navController = findNavController(R.id.navHostFragmentContainerView) val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottom_navigation) // 绑定底部导航和NavController bottomNavigationView.setupWithNavController(navController) } override fun onSupportNavigateUp(): Boolean { return navController.navigateUp() || super.onSupportNavigateUp() } }
方案二:保留ViewPager2,修复导航逻辑
如果一定要保留ViewPager2,需要确保点击按钮时,当前导航目的地确实是StaveniFragment:
修改StaveniFragment.kt的点击事件逻辑,添加目的地检查:
stavzacButton.setOnClickListener { val navController = findNavController() // 检查当前导航目的地是否是StaveniFragment if (navController.currentDestination?.id == R.id.staveniFragment) { navController.navigate(R.id.action_staveniFragment_to_staveniZacatecnikFragment) } }
另外,当从StaveniZacatecnikFragment返回时,确保ViewPager2的页面切换正确,或者在切换回Staveni页面时重置Fragment实例(可以通过ViewPagerAdapter的createFragment方法每次创建新实例,但这会影响性能)。
内容的提问来源于stack exchange,提问作者Omega
相关产品推荐
相关产品推荐

