たかぎとねこの忘備録

プログラミングに関する忘備録を自分用に残しときます。マサカリ怖い。

NavigationBarのNavigationBarItemを何度もタップすることにより、アプリが強制終了する場合の対処法

NavigationBarNavigationBarItemを何度もタップし、最後に一番左のNavigationBarItemを連打するとアプリがクラッシュする現象に遭遇した。

これがその時のエラーの抜粋。

java.util.NoSuchElementException: List contains no element matching the predicate.
    at androidx.navigation.compose.NavHostKt$NavHost$4.invoke(NavHost.kt:180)
    at androidx.navigation.compose.NavHostKt$NavHost$4.invoke(NavHost.kt:141)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:116)
    at androidx.compose.runtime.internal.ComposableLambdaImpl$invoke$1.invoke(ComposableLambda.jvm.kt:127)
    at androidx.compose.runtime.internal.ComposableLambdaImpl$invoke$1.invoke(ComposableLambda.jvm.kt:127)
    at androidx.compose.runtime.RecomposeScopeImpl.compose(RecomposeScopeImpl.kt:145)
    at androidx.compose.runtime.ComposerImpl.recomposeToGroupEnd(Composer.kt:2375)

例外が発生している箇所は、NavHostlast()メソッドの呼び出し部分だった。

// classes.jar/androidx/navigation/compose/NavHostKt.class

if (backStackEntry != null) {
    // while in the scope of the composable, we provide the navBackStackEntry as the
    // ViewModelStoreOwner and LifecycleOwner
    Crossfade(backStackEntry.id, modifier) {
        val lastEntry = visibleEntries.last { entry ->
            it == entry.id
        }

大元を辿るとnavigate()メソッドを呼び出している箇所によるものだった。

navController.navigate(newRoute) {
    popUpTo(navController.graph.findStartDestination().id) {}
    launchSingleTop = true
}

どうやら、特定の条件下において同じ遷移先に対して何度もnavigate()メソッドを呼び出そうとするとクラッシュするようだ。

なので、これから遷移しようとしている先が現在のルートと被っていないかを確認する処理を挟んであげることで解決した。

fun navigateAndPopUp(
    destination: TnaNavigationDestination,
    route: String? = null,
    from: NavBackStackEntry? = try { navController.currentBackStackEntry } catch(err: Exception) { null }
) {
    if (from != null && from.lifecycleIsResumed()) {
        val newRoute = when (destination) {
            is TopLevelDestination -> {
                destination.destination
            }
            else -> {
                route ?: destination.route
            }
        }
        when(newRoute) {
            from.destination.route -> { /* 何もしない */ }
            else -> {
                navController.navigate(newRoute) {
                    popUpTo(navController.graph.findStartDestination().id) {}
                    launchSingleTop = true
                }
            }
        }
    }
}

参考

Google Issue Tracker

kotlin Exception「Exception in thread “main” java.util.NoSuchElementException: Collection contains no element matching the predicate.」の解決方法 | mebee

android - Collection contains no element matching the predicate - Stack Overflow