Compose и другие библиотеки

Вы можете использовать свои любимые библиотеки в Compose. В этом разделе описывается, как подключить несколько наиболее полезных библиотек.

Активность

Чтобы использовать Compose в действии, вы должны использовать ComponentActivity , подкласс Activity , который предоставляет соответствующий LifecycleOwner и компоненты для Compose. Он также предоставляет дополнительные API, которые отделяют ваш код от переопределения методов в вашем классе активности. Activity Compose предоставляет эти API-интерфейсы составным объектам, так что переопределение методов вне ваших составных объектов или получение явного экземпляра Activity больше не требуется. Более того, эти API гарантируют, что они инициализируются только один раз, выдерживают рекомпозицию и правильно очищаются, если компонуемый объект удаляется из композиции.

Результат деятельности

API rememberLauncherForActivityResult() позволяет вам получить результат действия в вашем компонуемом объекте:

@Composable
fun GetContentExample() {
    var imageUri by remember { mutableStateOf<Uri?>(null) }
    val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
        imageUri = uri
    }
    Column {
        Button(onClick = { launcher.launch("image/*") }) {
            Text(text = "Load Image")
        }
        Image(
            painter = rememberAsyncImagePainter(imageUri),
            contentDescription = "My Image"
        )
    }
}

В этом примере демонстрируется простой контракт GetContent() . Нажатие на кнопку запускает запрос. Завершающая лямбда-выражение для rememberLauncherForActivityResult() вызывается, когда пользователь выбирает изображение и возвращается к действию запуска. Это загружает выбранное изображение с помощью функции rememberImagePainter() Coil.

Любой подкласс ActivityResultContract можно использовать в качестве первого аргумента для rememberLauncherForActivityResult() . Это означает, что вы можете использовать этот метод для запроса контента из платформы и других распространенных шаблонов. Вы также можете создавать свои собственные контракты и использовать их с помощью этой техники.

Запрос разрешений во время выполнения

Тот же API результата действия и rememberLauncherForActivityResult() описанный выше, можно использовать для запроса разрешений во время выполнения с использованием контракта RequestPermission для одного разрешения или контракта RequestMultiplePermissions для нескольких разрешений.

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

Обработка кнопки возврата системы

Чтобы обеспечить настраиваемую обратную навигацию и переопределить поведение системной кнопки «Назад» по умолчанию из вашего составного объекта, ваш составной объект может использовать BackHandler для перехвата этого события:

var backHandlingEnabled by remember { mutableStateOf(true) }
BackHandler(backHandlingEnabled) {
    // Handle back press
}

Первый аргумент определяет, включен ли BackHandler в данный момент; вы можете использовать этот аргумент, чтобы временно отключить обработчик в зависимости от состояния вашего компонента. Завершающая лямбда-выражение будет вызвана, если пользователь инициирует системное обратное событие, а BackHandler в данный момент включен.

ViewModel

Если вы используете библиотеку ViewModel компонентов архитектуры , вы можете получить доступ к ViewModel из любого компонуемого объекта, вызвав функцию viewModel() . Добавьте следующую зависимость в ваш файл Gradle:

классный

dependencies {
    implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.8.5'
}

Котлин

dependencies {
    implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.5")
}

Затем вы можете использовать функцию viewModel() в своем коде.

class MyViewModel : ViewModel() { /*...*/ }

// import androidx.lifecycle.viewmodel.compose.viewModel
@Composable
fun MyScreen(
    viewModel: MyViewModel = viewModel()
) {
    // use viewModel here
}

viewModel() возвращает существующую ViewModel или создает новую. По умолчанию возвращаемая ViewModel ограничена включающим действием, фрагментом или местом назначения навигации и сохраняется до тех пор, пока область активна.

Например, если в действии используется составной элемент, viewModel() возвращает один и тот же экземпляр до тех пор, пока действие не завершится или процесс не будет завершен.

class MyViewModel : ViewModel() { /*...*/ }
// import androidx.lifecycle.viewmodel.compose.viewModel
@Composable
fun MyScreen(
    // Returns the same instance as long as the activity is alive,
    // just as if you grabbed the instance from an Activity or Fragment
    viewModel: MyViewModel = viewModel()
) { /* ... */ }

@Composable
fun MyScreen2(
    viewModel: MyViewModel = viewModel() // Same instance as in MyScreen
) { /* ... */ }

Рекомендации по использованию

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

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

Вы можете использовать экземпляры ViewModel для управления состоянием составных элементов на уровне подэкрана , однако помните о жизненном цикле и области действия ViewModel . Если составной объект является автономным, вы можете рассмотреть возможность использования Hilt для внедрения ViewModel , чтобы избежать необходимости передавать зависимости от родительских составных объектов.

Если ваша ViewModel имеет зависимости, viewModel() принимает необязательный ViewModelProvider.Factory в качестве параметра.

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

Потоки данных

Compose поставляется с расширениями для самых популярных потоковых решений Android. Каждое из этих расширений предоставляется отдельным артефактом:

  • LiveData.observeAsState() включен в артефакт androidx.compose.runtime:runtime-livedata:$composeVersion .
  • Flow.collectAsState() не требует дополнительных зависимостей.
  • Observable.subscribeAsState() включен в артефакт androidx.compose.runtime:runtime-rxjava2:$composeVersion или androidx.compose.runtime:runtime-rxjava3:$composeVersion .

Эти артефакты регистрируются как прослушиватель и представляют значения как State . Всякий раз, когда создается новое значение, Compose перекомпоновывает те части пользовательского интерфейса, где используется это state.value . Например, в этом коде ShowData перекомпоновывается каждый раз, когда exampleLiveData выдает новое значение.

// import androidx.lifecycle.viewmodel.compose.viewModel
@Composable
fun MyScreen(
    viewModel: MyViewModel = viewModel()
) {
    val dataExample = viewModel.exampleLiveData.observeAsState()

    // Because the state is read here,
    // MyScreen recomposes whenever dataExample changes.
    dataExample.value?.let {
        ShowData(dataExample)
    }
}

Асинхронные операции в Compose

Jetpack Compose позволяет выполнять асинхронные операции с использованием сопрограмм из ваших компонуемых объектов.

Дополнительные сведения см. в API-интерфейсах LaunchedEffect , produceState и rememberCoroutineScope в документации по побочным эффектам .

Компонент навигации обеспечивает поддержку приложений Jetpack Compose. Дополнительные сведения см. в разделе «Навигация с помощью Compose» и «Миграция Jetpack Navigation в Navigation Compose» .

Рукоять

Hilt — рекомендуемое решение для внедрения зависимостей в приложениях Android, которое прекрасно работает с Compose.

Функция viewModel() упомянутая в разделе ViewModel, автоматически использует ViewModel, которую Hilt конструирует с аннотацией @HiltViewModel . Мы предоставили документацию с информацией об интеграции Hilt ViewModel .

@HiltViewModel
class MyViewModel @Inject constructor(
    private val savedStateHandle: SavedStateHandle,
    private val repository: ExampleRepository
) : ViewModel() { /* ... */ }

// import androidx.lifecycle.viewmodel.compose.viewModel
@Composable
fun MyScreen(
    viewModel: MyViewModel = viewModel()
) { /* ... */ }

Рукоять и навигация

Hilt также интегрируется с библиотекой Navigation Compose. Добавьте следующие дополнительные зависимости в ваш файл Gradle:

классный

dependencies {
    implementation 'androidx.hilt:hilt-navigation-compose:1.2.0'
}

Котлин

dependencies {
    implementation("androidx.hilt:hilt-navigation-compose:1.2.0")
}

При использовании Navigation Compose всегда используйте составную функцию hiltViewModel для получения экземпляра вашего ViewModel с аннотацией @HiltViewModel . Это работает с фрагментами или действиями, которые помечены @AndroidEntryPoint .

Например, если ExampleScreen является пунктом назначения в графе навигации, вызовите hiltViewModel() чтобы получить экземпляр ExampleViewModel привязанный к месту назначения, как показано в фрагменте кода ниже:

// import androidx.hilt.navigation.compose.hiltViewModel

@Composable
fun MyApp() {
    val navController = rememberNavController()
    val startRoute = "example"
    NavHost(navController, startDestination = startRoute) {
        composable("example") { backStackEntry ->
            // Creates a ViewModel from the current BackStackEntry
            // Available in the androidx.hilt:hilt-navigation-compose artifact
            val viewModel = hiltViewModel<MyViewModel>()
            MyScreen(viewModel)
        }
        /* ... */
    }
}

Если вместо этого вам нужно получить экземпляр ViewModel привязанный к навигационным маршрутам или графу навигации , используйте составную функцию hiltViewModel и передайте соответствующий backStackEntry в качестве параметра:

// import androidx.hilt.navigation.compose.hiltViewModel
// import androidx.navigation.compose.getBackStackEntry

@Composable
fun MyApp() {
    val navController = rememberNavController()
    val startRoute = "example"
    val innerStartRoute = "exampleWithRoute"
    NavHost(navController, startDestination = startRoute) {
        navigation(startDestination = innerStartRoute, route = "Parent") {
            // ...
            composable("exampleWithRoute") { backStackEntry ->
                val parentEntry = remember(backStackEntry) {
                    navController.getBackStackEntry("Parent")
                }
                val parentViewModel = hiltViewModel<ParentViewModel>(parentEntry)
                ExampleWithRouteScreen(parentViewModel)
            }
        }
    }
}

Пейджинг

Библиотека подкачки упрощает постепенную загрузку данных и поддерживается в Compose. Страница выпуска Paging содержит информацию о дополнительной зависимости paging-compose , которую необходимо добавить в проект, и его версии.

Вот пример API Compose библиотеки подкачки:

@Composable
fun MyScreen(flow: Flow<PagingData<String>>) {
    val lazyPagingItems = flow.collectAsLazyPagingItems()
    LazyColumn {
        items(
            lazyPagingItems.itemCount,
            key = lazyPagingItems.itemKey { it }
        ) { index ->
            val item = lazyPagingItems[index]
            Text("Item is $item")
        }
    }
}

Ознакомьтесь с документацией по спискам и сеткам для получения дополнительной информации об использовании разбиения на страницы в Compose.

Карты

Вы можете использовать библиотеку Maps Compose , чтобы добавить Карты Google в свое приложение. Вот пример использования:

@Composable
fun MapsExample() {
    val singapore = LatLng(1.35, 103.87)
    val cameraPositionState = rememberCameraPositionState {
        position = CameraPosition.fromLatLngZoom(singapore, 10f)
    }
    GoogleMap(
        modifier = Modifier.fillMaxSize(),
        cameraPositionState = cameraPositionState
    ) {
        Marker(
            state = remember { MarkerState(position = singapore) },
            title = "Singapore",
            snippet = "Marker in Singapore"
        )
    }
}

{% дословно %} {% дословно %} {% дословно %} {% дословно %}