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