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 Activity Result и rememberLauncherForActivityResult() описанные выше, можно использовать для запроса разрешений времени выполнения с использованием контракта RequestPermission для одного разрешения или контракта RequestMultiplePermissions для нескольких разрешений.

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

Обработка системной кнопки «Назад»

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

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

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

ViewModel

Если вы используете библиотеку Architecture Components 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 в документации по побочным эффектам .

Компонент Navigation обеспечивает поддержку приложений Jetpack Compose. Для получения дополнительной информации см. Navigating with Compose и Migrate Jetpack Navigation to Navigation Compose.

Эфес

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

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

@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 для получения экземпляра вашего @HiltViewModel аннотированного ViewModel . Это работает с фрагментами или действиями, которые аннотированы @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)
            }
        }
    }
}

Пейджинг

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

Вот пример API Compose библиотеки Paging:

@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 Maps в вашем приложении. Вот пример использования:

@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"
        )
    }
}

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

Вы можете использовать ваши любимые библиотеки в 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 Activity Result и rememberLauncherForActivityResult() описанные выше, можно использовать для запроса разрешений времени выполнения с использованием контракта RequestPermission для одного разрешения или контракта RequestMultiplePermissions для нескольких разрешений.

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

Обработка системной кнопки «Назад»

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

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

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

ViewModel

Если вы используете библиотеку Architecture Components 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 в документации по побочным эффектам .

Компонент Navigation обеспечивает поддержку приложений Jetpack Compose. Для получения дополнительной информации см. Navigating with Compose и Migrate Jetpack Navigation to Navigation Compose.

Эфес

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

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

@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 для получения экземпляра вашего @HiltViewModel аннотированного ViewModel . Это работает с фрагментами или действиями, которые аннотированы @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)
            }
        }
    }
}

Пейджинг

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

Вот пример API Compose библиотеки Paging:

@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 Maps в вашем приложении. Вот пример использования:

@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"
        )
    }
}

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