Как перейти с Jetpack Navigation на Navigation Compose

Navigation Compose API позволяет переходить между элементами composable в приложении Compose, используя компонент Jetpack Navigation, его инфраструктуру и функции.

На этой странице рассказывается, как перейти с навигации Jetpack на основе фрагментов на навигацию Compose в рамках более масштабного перехода с интерфейса на основе представлений на Jetpack Compose.

Требования для переноса

Перейти на Navigation Compose можно, когда вы сможете заменить все фрагменты на соответствующие экраны, созданные с помощью Compose. Компоненты экрана могут содержать контент Compose и View, но все пункты назначения должны быть компонентами, чтобы можно было перейти на Navigation Compose. До этого времени вам следует продолжать использовать компонент навигации на основе фрагментов в коде View и Compose. Дополнительную информацию можно найти в документации по взаимодействию навигации.

Этапы переноса

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

Если ваше приложение уже соответствует шаблону проектирования UDF и нашему руководству по архитектуре, при переходе на Jetpack Compose и Navigation Compose вам не придется значительно перерабатывать другие уровни приложения, кроме уровня интерфейса.

Чтобы перейти на Navigation Compose, выполните следующие действия:

  1. Добавьте в приложение зависимость Navigation Compose.
  2. Создайте composable-функцию App-level и добавьте ее в Activity в качестве точки входа Compose, заменив настройку макета View:

    class SampleActivity : ComponentActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            // setContentView<ActivitySampleBinding>(this, R.layout.activity_sample)
            setContent {
                SampleApp(/* ... */)
            }
        }
    }

  3. Создайте типы для каждого целевого местоположения. Используйте data object для целевых страниц, на которых не требуются данные, и data class или class для целевых страниц, на которых данные требуются.

    @Serializable data object First
    @Serializable data class Second(val id: String)
    @Serializable data object Third
    

  4. Настройте NavController в месте, где все композиции, которым нужно на него ссылаться, имеют к нему доступ (обычно это внутри композиции App). Этот подход соответствует принципам передачи состояния и позволяет использовать NavController в качестве источника достоверных данных для перехода между composable-функциями и сохранения обратного стека:

    @Composable
    fun SampleApp() {
        val navController = rememberNavController()
        // ...
    }

  5. Создайте NavHost приложения внутри composable-функции App и передайте следующие параметры:navController

    @Composable
    fun SampleApp() {
        val navController = rememberNavController()
    
        SampleNavHost(navController = navController)
    }
    
    @Composable
    fun SampleNavHost(
        navController: NavHostController
    ) {
        NavHost(navController = navController, startDestination = First) {
            // ...
        }
    }

  6. Добавьте пункты назначения composable, чтобы создать навигационный граф. Если каждый экран уже был перенесен в Compose, на этом этапе нужно только извлечь эти компонуемые элементы экрана из фрагментов в пункты назначения composable:

    class FirstFragment : Fragment() {
    
        override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View {
            return ComposeView(requireContext()).apply {
                setContent {
                    // FirstScreen(...) EXTRACT FROM HERE
                }
            }
        }
    }
    
    @Composable
    fun SampleNavHost(
        navController: NavHostController
    ) {
        NavHost(navController = navController, startDestination = First) {
            composable<First> {
                FirstScreen(/* ... */) // EXTRACT TO HERE
            }
            composable<Second> {
                SecondScreen(/* ... */)
            }
            // ...
        }
    }

  7. Если вы следовали рекомендациям по проектированию интерфейса Compose, в частности о том, как передавать ViewModel и события навигации в composable-функции, следующий шаг – изменить способ передачи ViewModel в каждую composable-функцию экрана. Часто можно использовать внедрение зависимостей Hilt и его точку интеграции с Compose и Navigation через hiltViewModel:

    @Composable
    fun FirstScreen(
        // viewModel: FirstViewModel = viewModel(),
        viewModel: FirstViewModel = hiltViewModel(),
        onButtonClick: () -> Unit = {},
    ) {
        // ...
    }

  8. Замените все вызовы навигации findNavController() на вызовы navController и передавайте их в качестве событий навигации на каждый экран, а не передавайте весь объект navController. Этот подход соответствует рекомендациям по передаче событий из composable-функций вызывающим функциям и позволяет использовать navController в качестве единого источника достоверных данных.

    Чтобы передать данные в пункт назначения, создайте экземпляр класса маршрута, определенного для этого пункта назначения. Его можно получить либо напрямую из записи в стеке переходов на целевой странице, либо из объекта ViewModel с помощью метода SavedStateHandle.toRoute().

    @Composable
    fun SampleNavHost(
        navController: NavHostController
    ) {
        NavHost(navController = navController, startDestination = First) {
            composable<First> {
                FirstScreen(
                    onButtonClick = {
                        // findNavController().navigate(firstScreenToSecondScreenAction)
                        navController.navigate(Second(id = "ABC"))
                    }
                )
            }
            composable<Second> { backStackEntry ->
                val secondRoute = backStackEntry.toRoute<Second>()
                SecondScreen(
                    id = secondRoute.id,
                    onIconClick = {
                        // findNavController().navigate(secondScreenToThirdScreenAction)
                        navController.navigate(Third)
                    }
                )
            }
            // ...
        }
    }

  9. Удалите все фрагменты, связанные макеты XML, ненужные элементы навигации и другие ресурсы, а также устаревшие зависимости фрагментов и Jetpack Navigation.

Те же действия, но с более подробной информацией о Navigation Compose, описаны в документации по настройке.

Примеры использования

Независимо от того, какой компонент Navigation вы используете, принципы навигации остаются неизменными.

При переносе данных часто используются следующие сценарии:

Подробную информацию об этих вариантах использования можно найти в разделе Навигация с помощью Compose.

Получение сложных данных во время навигации

Мы настоятельно рекомендуем не передавать сложные объекты данных при переходе между страницами. Вместо этого передавайте в качестве аргументов при выполнении действий навигации минимально необходимую информацию, например уникальный идентификатор или другой тип идентификатора. Сложные объекты следует хранить в виде данных в едином источнике достоверной информации, например в слое данных. Подробнее о том, как получать сложные данные при навигации…

Если ваши фрагменты передают сложные объекты в качестве аргументов, сначала попробуйте провести рефакторинг кода так, чтобы эти объекты можно было хранить и извлекать из слоя данных. Примеры можно найти в репозитории Now in Android.

Ограничения

В этом разделе описаны текущие ограничения Navigation Compose.

Постепенный переход на Navigation Compose

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

Анимация перехода

Начиная с версии Navigation 2.7.0-alpha01, поддержка настройки специальных переходов, ранее доступная в AnimatedNavHost, теперь реализована непосредственно в NavHost. Более подробные сведения можно найти в примечаниях к выпуску.

Подробнее…

Дополнительную информацию о переходе на Navigation Compose можно найти в следующих ресурсах:

  • Практическая работа "Navigation Compose". Изучите основы Navigation Compose с помощью практической работы.
  • Репозиторий Now in Android. Полнофункциональное приложение для Android, полностью созданное с помощью Kotlin и Jetpack Compose. В нем реализованы рекомендации по дизайну и разработке для Android, а также используется Navigation Compose.
  • Перенос приложения Sunflower на Jetpack Compose. В этой записи блога рассказывается о том, как перенести пример приложения Sunflower с Views на Compose, а также на Navigation Compose.
  • Jetnews для всех экранов. В этой записи блога рассказывается о рефакторинге и переносе примера Jetnews для поддержки всех экранов с помощью Jetpack Compose и Navigation Compose.