I'm building my own app in Android
I'm using Navigation component
to navigate between fragments.
It's logged IllegalArgumentException
: Navigation action/destination cannot be found from the current destination in Crashlytics of Firebase. Testers and I have tried the test many times but haven't been able to reproduce that problem and it didn't crash.
Here is the log:
# Crashlytics - Stack trace
# Application: com.highsecure.drawsketch.sketchpaint
# Platform: android
# Version: 1.3.7 (21)
# Issue: 5e74ae9f3447101130fd8f601b280d25
# Session: 66B3A1F5002300017A6E5585CD6044CF_DNE_0_v2
# Date: Wed Aug 07 2024 23:33:58 GMT+0700 (Indochina Time)
Fatal Exception: java.lang.IllegalArgumentException: Navigation action/destination com.highsecure.drawsketch.sketchpaint:id/action_mainFragment_to_dashBoardFragment cannot be found from the current destination Destination(com.highsecure.drawsketch.sketchpaint:id/dashBoardFragment) label=fragment_dash_board class=com.example.ardrawsketch.ui.dashboard.DashBoardFragment
at androidx.navigation.NavController.navigate(NavController.kt:1691)
at androidx.navigation.NavController.navigate(NavController.kt:1609)
at androidx.navigation.NavController.navigate(NavController.kt:1591)
at androidx.navigation.NavController.navigate(NavController.kt:1574)
at com.example.ardrawsketch.ui.activity.MainActivity$setupViews$1$1$1.invokeSuspend(MainActivity.kt:54)
at com.example.ardrawsketch.ui.activity.MainActivity$setupViews$1$1$1.invoke(:12)
at com.example.ardrawsketch.ui.activity.MainActivity$setupViews$1$1$1.invoke(:8)
at kotlinx.coroutines.flow.FlowKt__ReduceKt$first$$inlined$collectWhile$2.emit(Limit.kt:142)
at kotlinx.coroutines.flow.StateFlowImpl.collect(StateFlow.kt:396)
at kotlinx.coroutines.flow.FlowKt__ReduceKt.first(Reduce.kt:213)
at kotlinx.coroutines.flow.FlowKt.first(:1)
at com.example.ardrawsketch.ui.activity.MainActivity$setupViews$1$1.invokeSuspend(MainActivity.kt:43)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:365)
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:30)
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable$default(Cancellable.kt:25)
at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:110)
at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:126)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(Builders.common.kt:56)
at kotlinx.coroutines.BuildersKt.launch(:1)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch$default(Builders.common.kt:47)
at kotlinx.coroutines.BuildersKt.launch$default(:1)
at com.example.ardrawsketch.ui.activity.MainActivity.setupViews$lambda$0(MainActivity.kt:42)
at com.example.ardrawsketch.ui.activity.MainActivity.$r8$lambda$1WSZW11IMUxPs7-1lyyEmF1Ssik()
at com.example.ardrawsketch.ui.activity.MainActivity$$ExternalSyntheticLambda0.run(D8$$SyntheticClass)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:226)
at android.os.Looper.loop(Looper.java:313)
at android.app.ActivityThread.main(ActivityThread.java:8663)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:571)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1135)
Here is my code in version that older than 1.7.3 version:
@AndroidEntryPoint
class MainActivity :
BaseActivity<ActivityMainBinding, ActivityMainViewModel>(R.id.nav_host, R.navigation.nav_main) {
...
override fun setupViews() {
setStatusBarColor(R.color.white)
binding.main.post {
when (loginState) {
OnBoardingState.FIRST_TIME.value -> {
if (isLanguageSet) {
47 -> navHostController.navigate(R.id.action_mainFragment_to_onBoardingFragment)
} else {
navHostController.navigate(R.id.action_mainFragment_to_languageFragment)
}
}
OnBoardingState.FINISHED.value -> {
54 -> navHostController.navigate(R.id.action_mainFragment_to_dashBoardFragment)
}
}
true
}
}
...
}
Here is my 1.7.3 version code:
@AndroidEntryPoint
class MainActivity :
BaseActivity<ActivityMainBinding, ActivityMainViewModel>(R.id.nav_host, R.navigation.nav_main) {
...
override fun setupViews() {
setStatusBarColor(R.color.white)
binding.main.post {
when (loginState) {
LoginState.FIRST_TIME.value -> {
if (isLanguageSet) {
safeNavigate(
navHostController?.currentDestination?.id,
R.id.action_mainFragment_to_onBoardingFragment
)
} else {
safeNavigate(
navHostController?.currentDestination?.id,
R.id.action_mainFragment_to_languageFragment
)
}
}
LoginState.LOGGED_IN.value -> {
safeNavigate(
navHostController?.currentDestination?.id,
R.id.action_mainFragment_to_dashBoardFragment
)
}
}
}
}
...
}
abstract class BaseActivity<Binding : ViewBinding, ViewModel : androidx.lifecycle.ViewModel> :
AppCompatActivity {
@IdRes
private var navId: Int? = null
@NavigationRes
private var navResource: Int? = null
/**
* The NavController for the activity's navigation graph.
*/
protected var navHostController: NavController? = null
/**
* The ViewBinding for the activity.
*/
protected lateinit var binding: Binding
/**
* This method should return the ViewBinding for the activity.
*/
protected abstract fun getViewBinding(): Binding
/**
* The ViewModel for the activity.
*/
protected lateinit var viewModel: ViewModel
/**
* This method should return the Class object for the ViewModel.
*/
protected abstract fun getViewModelClass(): Class<ViewModel>
constructor()
constructor(@IdRes navId: Int, @NavigationRes navResource: Int) {
this.navId = navId
this.navResource = navResource
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = getViewBinding()
viewModel = ViewModelProvider(this)[getViewModelClass()]
setContentView(binding.root)
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
init()
}
private fun init() {
setupNav()
setupViews()
}
/**
* Set up the navigation for the activity.
*/
private fun setupNav() {
if (navId != null && navResource != null) {
val navHostFragment =
supportFragmentManager.findFragmentById(navId!!) as NavHostFragment
if (navHostController == null) {
navHostController = navHostFragment.navController
navHostController?.setGraph(navResource!!, intent.extras)
}
}
}
/**
* This method should set up the views for the activity.
*/
abstract fun setupViews()
protected fun safeNavigate(
currentDestination: Int?,
action: Int,
bundle: Bundle? = null
) {
if (navHostController?.currentDestination?.id == currentDestination) {
lifecycle.addObserver(object : LifecycleEventObserver {
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (event == Lifecycle.Event.ON_RESUME) {
lifecycle.removeObserver(this)
try {
navHostController?.navigate(action, bundle)
} catch (e: IllegalArgumentException) {
e.printStackTrace()
}
}
}
})
}
}
protected fun safeNavigate(
currentDestination: Int?,
action: NavDirections
) {
if (navHostController?.currentDestination?.id == currentDestination) {
lifecycle.addObserver(object : LifecycleEventObserver {
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (event == Lifecycle.Event.ON_RESUME) {
lifecycle.removeObserver(this)
try {
navHostController?.navigate(action)
} catch (e: IllegalArgumentException) {
e.printStackTrace()
}
}
}
})
}
}
}
I tried to catch the IllegalArgumentException
error but it's still logged in the crashlytics of Firebase:
protected fun safeNavigate(
currentDestination: Int?,
action: NavDirections
) {
if (navHostController?.currentDestination?.id == currentDestination) {
lifecycle.addObserver(object : LifecycleEventObserver {
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (event == Lifecycle.Event.ON_RESUME) {
lifecycle.removeObserver(this)
try {
navHostController?.navigate(action)
} catch (e: IllegalArgumentException) {
e.printStackTrace()
}
}
}
})
}
}
The message clearly says what the issue is. For some reason the triggered destination doesn't exists at runtime. These might be the causes of crash:
type in navigation graph xml file, just double check ids which you use in navigation.xml. Typos are not checked during compile time.
attempt to navigate to the destination from time beeing at this destination. It might happen because of transition animation glitches for instance. So doing navigation safelly will solve the crash issue. The kotlin solution which will make sure of desired destination existance at runtime might looks like:
fun NavController.safeNavigate(direction: NavDirections) {
currentDestination?.getAction(direction.actionId)?.run { navigate(direction) }
}