Изучаем Android-разработку

Действительно общая модель просмотра в Jetpack Compose

Легкая навигация и внедрение зависимостей с помощью Hilt

Введение

Добро пожаловать в очаровательный мир Jetpack Compose, где декларативный пользовательский интерфейс воплощает в жизнь ваше Android-приложение с элегантностью и простотой. В этом завораживающем путешествии мы узнаем, как использовать силу Hilt, волшебника внедрения зависимостей, и использовать магию Composition Locals для упрощения навигации в вашем Android-приложении на основе Jetpack Compose. Приготовьтесь быть зачарованными, когда мы раскроем секреты архитектурного подхода, который экономит время разработки и создает кодовую базу, которую легче поддерживать.

Давайте сделаем это

1. Сила модели Hilt and View

Hilt — это надежная библиотека внедрения зависимостей, которая легко интегрируется с компонентами Jetpack, что делает ее идеальным выбором для управления зависимостями в современных приложениях для Android. Одним из основных преимуществ Hilt является его способность автоматически внедрять зависимости в классы модели представления с помощью @HiltViewModel.

@HiltViewModel
internal class AppViewModel @Inject constructor() : ViewModel() {
    lateinit var navController: NavHostController
}

В нашем приложении мы используем AppViewModel, который расширяет ViewModel, для хранения и управления данными, связанными с пользовательским интерфейсом. Аннотируя класс AppViewModel с помощью @HiltViewModel, мы позволяем Hilt автоматически предоставлять экземпляры ViewModel везде, где они необходимы в приложении. Это устраняет шаблонный код, такой как ручная ViewModel заводская настройка, и гарантирует, что каждый ViewModel правильно привязан к связанному с ним Composable.

2. Обработка навигации с помощью NavHostController

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

Чтобы решить эту проблему, мы представляем CompositionLocals, мощную функцию Jetpack Compose. С помощью LocalAppViewModel мы создаем пользовательский CompositionLocal, который позволяет нам совместно использовать экземпляр AppViewModel и связанный с ним NavHostController во всем приложении без явной передачи параметров. Используя CompositionLocalProvider, мы гарантируем, что AppViewModelInfo доступен для всех составных функций ниже иерархии, где используется LocalAppViewModel.

internal val LocalAppViewModel = compositionLocalOf { AppViewModelInfo() }

@Composable
fun ProvideAppViewModel(
    navHostController: NavHostController,
    content: @Composable () -> Unit
) {
    val appViewModel: AppViewModel = hiltViewModel()

    appViewModel.navController = navHostController

    val appViewModelInfo = AppViewModelInfo(appViewModel)

    val state = remember { appViewModelInfo }

    CompositionLocalProvider(
        LocalAppViewModel provides state
    ) {
        content()
    }
}

3. Собираем все вместе

В составной функции ProvideAppViewModel мы объединяем возможности Hilt и CompositionLocals. Во-первых, мы используем hiltViewModel() для получения экземпляра AppViewModel, автоматически внедряемого Hilt. Затем мы присваиваем navHostController, полученное в качестве параметра, свойству navController элемента AppViewModel. Эта ассоциация позволяет нам эффективно управлять навигацией между различными экранами без передачи параметров по воронке.

С помощью CompositionLocalProvider мы предоставляем экземпляр AppViewModelInfo дочерним составным функциям, делая AppViewModel и связанный с ним navController доступными во всей составной иерархии приложения. Эта архитектура значительно сокращает шаблонный код и обеспечивает более организованную и удобную в сопровождении кодовую базу.

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

Предположим, у нас есть три компонуемые функции, Screen A Screen B и Screen C, представляющие разные экраны / компонуемые в нашем приложении.

@Composable
fun ScreenA() {
    val navController = rememberNavController()
    
    // Using the new approach with Composition Locals to 
    // provide the AppViewModel
    ProvideAppViewModel(navController) {
      NavHost(navController = navController, startDestination = AppRoutes.SCREEN_A.name) {
            composable(SCREEN_A.name) {
                ScreenA()
            }

            composable(SCREEN_B.name) {
                ScreenB()
            }

            composable(SCREEN_C.name) {
                ScreenC()
            }
      }
    }
}

В старом способе передачи navController вам пришлось бы явно передавать navController в качестве параметра каждому составному объекту, который в нем нуждается. Например:

@Composable
fun OldScreenB(navController: NavHostController) {
    // Access the navController in this composable
}

@Composable
fun OldScreenC(navController: NavHostController) {
    // Access the navController in this composable
}

С новым подходом, использующим Composition Locals, нам больше не нужно явно передавать navController в качестве параметра каждому составному объекту, который его требует. Вместо этого мы предоставляем AppViewModel и связанный с ним navController с помощью CompositionLocalProvider, и любая компонуемая функция ниже иерархии, где используется LocalAppViewModel, может получить к ним прямой доступ.

@Composable
fun ScreenB() {
    // No need to pass navController as a parameter
    // It is automatically available through Composition Locals
    // Access the AppViewModel and its navController in this composable and its children
    val appViewModelInfo = LocalAppViewModel.current
    val appViewModel = appViewModelInfo.appViewModel

    // Navigate back to ScreenA
    Button(onClick = { appViewModel?.navController?.popBackStack() }) {
        Text("Go back to Screen A")
    }
}

Распространение этого подхода на темы

Универсальность CompositionLocals выходит за рамки внедрения ViewModel и навигации. Предположим, наше приложение поддерживает разные темы, например светлый и темный режимы. Мы можем использовать CompositionLocals для совместного использования текущих данных темы по всей компонуемой иерархии без необходимости явно передавать параметры и хранить их только в нашей модели представления. Мы можем еще больше расширить его, настроив его удаленно и всегда имея один источник достоверной информации о теме.

val theme = LocalAppViewModel.current.appViewModel.mytheme

Заключение

Использование Hilt для ViewModel инъекции и CompositionLocals для навигации в приложениях Jetpack Compose упрощает процесс разработки, делая его более интуитивно понятным и эффективным. Используя возможности этих библиотек, мы избавляемся от громоздких задач настройки и сосредотачиваемся на написании чистого и лаконичного кода. Полученная архитектура не только упрощает управление зависимостями, но и повышает читабельность кода.

В заключение, использование Hilt и CompositionLocals — это мощный шаг к созданию современных, надежных и удобных в сопровождении приложений для Android, которые сияют великолепием парадигмы декларативного пользовательского интерфейса Jetpack Compose. Итак, зачем ждать? Начните внедрять эти концепции в свои проекты и станьте свидетелем совершенно нового уровня разработки приложений для Android.

Примечание. Правильное использование локальных переменных композиции

Хотя Composition Locals предлагает мощный способ обмена данными и зависимостями по компонуемой иерархии в Jetpack Compose, важно использовать их разумно и избегать чрезмерного использования. Локальные объекты композиции наиболее эффективны при использовании на уровне приложения или в композициях родительского уровня, где данные или зависимости должны быть доступны для нескольких дочерних компонуемых функций.

Мысли?

Надеюсь, вам понравилось читать эту статью и вы узнали что-то новое или интересное!

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

Гитхаб

В моей последней статье я создал приложение для виртуального паспорта национальных парков, в котором я придумал этот код и стратегию. Вот ссылка на репозиторий и Kotlin File, который ее использует.

Соединять!

Не стесняйтесь подписываться на меня в LinkedIn, чтобы получать регулярные обновления многих других статей, которые я собираюсь написать о Compose и Android в целом. Я всегда стремлюсь к общению с другими разработчиками, обмену знаниями и созданию динамичного обучающего сообщества.

Удачного кодирования!