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, а BackHandler в данный момент включён.

ViewModel

Если вы используете библиотеку ViewModel от Architecture Components , вы можете получить доступ к 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 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 для получения экземпляра 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)
            }
        }
    }
}

Пейджинг

Библиотека 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 Карт в своё приложение. Вот пример использования:

@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 %}