Сбой типобезопасной навигации в Jetpack Compose

Вопросы и ответы

Восстановление навигационного стека не удалось: назначение 30355061 не может быть найдено из текущего назначения mj0(0x0) startDestination={oj0(0x2e873c) route=x.x.x.x.composeNavigation.MenuScreen}

У меня есть проект на Jetpack Compose, где я использую типобезопасную навигацию, и я вижу множество сбоев в Firebase.

import kotlinx.serialization.Serializable

@Serializable
object QrCodeScreenScanner

@Serializable
data class OtpScreen(val otpCode: String)

@Serializable
data class SignatureDataOverviewScreen(val transactionId: String, val payload: String)

@Serializable
data class SignatureHashOverlayScreen(val otpCode: String)

@Serializable
object MenuScreen

@Serializable
object AskForNotificationPermissionScreen

@Serializable
data class UpdateNeededScreen(val cancellable: Boolean)

    class MenuActivity : ComponentActivity() {

    companion object {
        const val ENROLMENT_COMPLETED = "ENROLMENT_COMPLETED"
    }

    private val navigator: Navigator by inject { parametersOf(this@MenuActivity) }
    private val menuViewModel: MenuViewModel by viewModel()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val navController = rememberNavController()
            var permissionDialogScreenAlreadyShowed = remember { false }

            Theme {
                NavHost(navController = navController, startDestination = MenuScreen) {
                    composable<MenuScreen> {
                        val uiState by menuViewModel.uiState.collectAsStateWithLifecycle()
                        MenuScreen(
                            uiState = uiState,
                            navigator = navigator,
                            navigateToAskForNotificationPermission = {
                                navController.navigate(AskForNotificationPermissionScreen)
                            },
                            enrolmentStates = menuViewModel.subscribeToEnrolmentState(),
                            permissionDialogScreenAlreadyShowed = permissionDialogScreenAlreadyShowed,
                            uiEvents = menuViewModel.uiEvent,
                            isOnline = menuViewModel.isOnline,
                            menuItemIntentAction = menuViewModel::setMenuViewModelIntent,
                            appUpdateCheckNeeded = menuViewModel.appUpdateCheckNeeded,
                            navigateToShowUpdateScreen = { cancellable ->
                                navController.navigate(UpdateNeededScreen(cancellable))
                            },
                        )
                    }
                    composable<AskForNotificationPermissionScreen> {
                        RequestNotificationPermissionScreen(
                            navigateToMenuScreen = {
                                permissionDialogScreenAlreadyShowed = true
                                navController.navigate(MenuScreen)
                            }
                        )
                    }
                    composable<UpdateNeededScreen> {
                        ShowAppUpdateScreen(
                            cancellable = true,
                            navigateToMenuScreen = {
                                navController.navigate(MenuScreen)
                            }
                        )
                    }
                }
            }
        }
    }

    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)

        intent.extras?.let { extras ->
            val confirmationToken =
                extras.getString(this.getString(R.string.ENROLMENT_CHALLENGE), "")
            val deviceName =
                extras.getString(this.getString(R.string.ENROLMENT_DEVICENAME), "")

            Timber.d("Confirmation token: %s", confirmationToken ?: "(пусто)")
            Timber.d("DeviceName: %s", deviceName ?: "(пусто)")
            if (!confirmationToken.isNullOrBlank() && !deviceName.isNullOrBlank()) {
                menuViewModel.setMenuViewModelIntent(
                    MenuViewModelIntent.ProcessEnrolment(
                        confirmationToken = confirmationToken,
                        deviceName = deviceName
                    )
                )
                return
            }
            val isEnrolmentCompleted = extras.getBoolean(ENROLMENT_COMPLETED, false)
            if (isEnrolmentCompleted) {
                navigator.navigateTo(NavigatorRoute.ENROLMENT_COMPLETED)
            }
        }
    }
}

У меня архитектура с несколькими модулями, где у каждого модуля есть своя навигационная графика.

Кто-то сталкивался с этой проблемой ранее? Я использую новый способ безопасности типов для навигации. Локально все работает как ожидалось, но я вижу много ошибок в Firebase Crashlytics. Почему?

Ответ

Ваша проблема связана с тем, что используется типобезопасная навигация в Jetpack Compose, и возникают ошибки с восстановлением стека навигации. Ошибка, которую вы видите, указывает, что приложение пытается вернуться к экрану, который не был найден в текущем навигационном графе.

Возможные причины проблемы и их решения:

  1. Неправильная конфигурация навигационного графа: Ваша архитектура с несколькими модулями может приводить к тому, что некоторые экраны не представлены в текущем графе навигации. Убедитесь, что при добавлении новых экранов вы обновляете все необходимые навигационные графы.

    Решение: Проверьте, что все экраны из всех модулей корректно добавлены в соответствующий навигационный граф. Убедитесь, что идентификаторы экранов (например, MenuScreen, AskForNotificationPermissionScreen и т.д.) не конфликтуют и правильно используются.

  2. Использование navController.navigate(): Пожалуйста, обратите внимание на то, как вы используете метод navigate(). Если вы пытаетесь выполнить навигацию к экрану, который в данный момент не доступен (например, экран не был инициализирован или уже был удалён из стека), это может привести к подобным ошибкам.

    Решение: Убедитесь, что переходы между экранами всегда происходят в контексте доступного стека навигации. Например, перед навигацией проверяйте состояние текущего экрана и его доступность.

  3. Обработка onNewIntent: Проверьте, как в вашем методе onNewIntent обрабатываются новые интенты. Если новое намерение приводит к навигации, которая еще не завершилась или не корректна, вы можете столкнуться с проблемами.

    Решение: Рассмотрите возможность проверки состояния перед вызовом навигации из onNewIntent. Например, добавьте дополнительные проверки и условия для выполнения навигации только в том случае, если текущее состояние это позволяет.

  4. Файлы ресурсов и локализация: Убедитесь, что в ваших ресурсах (например, строках) нет дублирующихся или недоступных значений, которые могут вызывать сбои.

  5. Логирование и отладка: Используйте инструменты логирования, такие как Timber, чтобы выводить состояния навигации на консоль. Это может дать вам представление о том, какие действия выполняются до возникновения ошибки.

    Пример логирования:

    Timber.d("Navigating to: ${destination.route}")

Заключение

Некоторые из этих проблем сложно выявить без подробного анализа вашего кода и архитектуры. Убедитесь, что каждая часть вашего приложения не зависит от другой в контексте навигации, и каждая навигация безопасна и контролируемая. Если проблема сохраняется, рассмотрите возможность предоставления более информации в вопросы поддержки для более детального анализа.

Оцените материал
Добавить комментарий

Капча загружается...