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

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

Активность

Для использования Compose в Activity необходимо использовать ComponentActivity — подкласс Activity , предоставляющий Compose соответствующий LifecycleOwner и компоненты. Он также предоставляет дополнительные API, которые отделяют ваш код от переопределения методов в вашем классе Activity. 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 Permissions также может использоваться на уровне выше этих 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 (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.3.0'
}

Котлин

dependencies {
    implementation("androidx.hilt:hilt-navigation-compose:1.3.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 , которую необходимо добавить в проект, и о её версии.

Вот пример использования Compose API библиотеки 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"
        )
    }
}

{% verbatim %} {% endverbatim %} {% verbatim %} {% endverbatim %}