Перенос навигации Jetpack в Navigation Compose

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

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

Предпосылки миграции

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

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

Этапы миграции

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

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

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

  1. Добавьте зависимость Navigation Compose в свое приложение.
  2. Создайте компонуемый объект 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
    fun SampleApp() {
        val navController = rememberNavController()
        // ...
    }

  5. Создайте NavHost вашего приложения внутри компонуемого объекта 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 UI , в частности, по передаче ViewModel и событий навигации в компонуемые элементы, следующим шагом будет изменение способа предоставления ViewModel для каждого компонуемого элемента экрана. Часто можно использовать внедрение Hilt и его точку интеграции с Compose и Navigation через hiltViewModel :

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

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

Те же шаги с более подробной информацией о создании навигации можно найти в документации по настройке .

Распространенные варианты использования

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

К распространенным вариантам использования при миграции относятся следующие:

Более подробную информацию об этих вариантах использования см. в разделе Навигация с помощью 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 с помощью практической лабораторной работы.
  • Теперь в репозитории Android : полнофункциональное приложение Android, полностью созданное с использованием Kotlin и Jetpack Compose, которое соответствует лучшим практикам проектирования и разработки Android и включает Navigation Compose.
  • Миграция Sunflower в Jetpack Compose : запись в блоге, в которой описывается процесс миграции примера приложения Sunflower из Views в Compose, включая миграцию в Navigation Compose.
  • Jetnews для каждого экрана : запись в блоге, в которой описывается рефакторинг и миграция примера Jetnews для поддержки всех экранов с помощью Jetpack Compose и Navigation Compose.
{% дословно %} {% endverbatim %} {% дословно %} {% endverbatim %}