Используйте сопрограммы Kotlin с компонентами, поддерживающими жизненный цикл

Корутины Kotlin предоставляют API, позволяющий писать асинхронный код. С помощью корутин Kotlin вы можете определить CoroutineScope , который помогает управлять временем выполнения ваших корутин. Каждая асинхронная операция выполняется в рамках определенной области видимости.

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

Добавить зависимости

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

  • Для использования вспомогательных функций ViewModel в Compose используйте implementation("androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version") .
  • Для утилит управления жизненным циклом в Compose используйте implementation("androidx.lifecycle:lifecycle-runtime-compose:$lifecycle_version") .

Сопрограммные области с учетом жизненного цикла

Библиотеки Compose и Lifecycle предоставляют следующие встроенные области видимости, которые вы можете использовать в своем приложении.

ViewModelScope

Для каждой ViewModel в вашем приложении определяется область ViewModelScope . Любая сопрограмма, запущенная в этой области видимости, автоматически отменяется, если ViewModel очищается. Сопрограммы здесь полезны, когда вам нужно выполнить работу только тогда, когда ViewModel активна. Например, если вы вычисляете данные для макета, вам следует ограничить область видимости работы ViewModel , чтобы при очистке ViewModel работа автоматически отменялась во избежание потребления ресурсов.

Доступ CoroutineScope объекта ViewModel можно получить через свойство viewModelScope этого ViewModel , как показано в следующем примере:

class MyViewModel: ViewModel() {
    init {
        viewModelScope.launch {
            // Coroutine that will be canceled when the ViewModel is cleared.
        }
    }
}

Для более сложных сценариев использования можно передать пользовательский CoroutineScope непосредственно в конструктор ViewModel, заменив стандартный viewModelScope . Такой подход обеспечивает больший контроль и гибкость, особенно для:

  • Тестирование: Оно позволяет внедрить TestScope , что упрощает контроль времени и проверку поведения сопрограмм в модульных тестах.

  • Пользовательская конфигурация: Вы можете настроить область действия с помощью конкретного CoroutineDispatcher (например, Dispatchers.Default для ресурсоемких вычислений) или пользовательского CoroutineExceptionHandler еще до начала работы ViewModel.

Области видимости, ограниченные композицией

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

Compose предоставляет API LaunchedEffect для декларативного управления областью видимости Compose.

LaunchedEffect создает CoroutineScope , позволяющий запускать приостановленные функции. Область видимости привязана к жизненному циклу композиции компонуемого объекта, а не к жизненному циклу хост-активности.

  • Вход: Сопрограмма запускается, когда компонуемый объект входит в композицию.
  • Выход: Сопрограмма отменяется, когда компонуемый объект покидает композицию.
  • Перезапуск: Если какой-либо ключ, переданный в LaunchedEffect , изменяется, существующая сопрограмма отменяется, и запускается новая.

Следующий пример демонстрирует, как использовать LaunchedEffect для создания пульсирующей анимации. Сопрограмма привязана к присутствию компонуемого объекта в композиции и реагирует на изменения конфигурации:

// Allow the pulse rate to be configured, so it can be sped up if the user is running
// out of time
var pulseRateMs by remember { mutableLongStateOf(3000L) }
val alpha = remember { Animatable(1f) }
LaunchedEffect(pulseRateMs) { // Restart the effect when the pulse rate changes
    while (isActive) {
        delay(pulseRateMs) // Pulse the alpha every pulseRateMs to alert the user
        alpha.animateTo(0f)
        alpha.animateTo(1f)
    }
}

Для получения дополнительной информации о LaunchedEffect см. раздел «Побочные эффекты в Compose» .

Сбор данных о потоках с учетом жизненного цикла

Для безопасного сбора данных о потоках в Jetpack Compose используйте API collectAsStateWithLifecycle . Эта единственная функция преобразует Flow в объект State Compose и автоматически управляет подпиской на жизненный цикл. По умолчанию сбор данных начинается, когда жизненный цикл STARTED , и заканчивается, когда он STOPPED . Чтобы изменить это поведение по умолчанию, передайте параметр minActiveState с нужным методом жизненного цикла, например, Lifecycle.State.RESUMED .

Следующий пример демонстрирует, как собрать StateFlow ViewModel в составном компоненте:

@Composable
private fun ConversationScreen(
    conversationViewModel: ConversationViewModel = viewModel()
) {

    val messages by conversationViewModel.messages.collectAsStateWithLifecycle()

    ConversationScreen(
        messages = messages,
        onSendMessage = { message: Message -> conversationViewModel.sendMessage(message) }
    )
}

@Composable
private fun ConversationScreen(
    messages: List<Message>,
    onSendMessage: (Message) -> Unit
) {

    MessagesList(messages, onSendMessage)
    /* ... */
}

Параллельное объединение множества потоков

В Compose можно собирать несколько потоков параллельно, объявляя несколько переменных состояния. Поскольку collectAsStateWithLifecycle управляет собственной областью видимости, параллельная сборка обрабатывается автоматически:

@Composable
fun DashboardScreen(viewModel: DashboardViewModel = viewModel()) {
    // Both flows are collected safely in parallel and will emit updates when either changes, the composables will recompose
    val userData by viewModel.userFlow.collectAsStateWithLifecycle()
    val feedData by viewModel.feedFlow.collectAsStateWithLifecycle()

    // ...
}

Асинхронный расчет значений с использованием потоков (Flows)

Если вам необходимо асинхронно вычислять значения, используйте StateFlow с оператором stateIn .

В следующем фрагменте кода используется стандартный Flow , преобразованный в StateFlow . Параметр WhileSubscribed(5000) поддерживает подписку активной в течение пяти секунд после исчезновения пользовательского интерфейса для обработки изменений конфигурации.

val uiState: StateFlow<Result> = flow {
    emit(repository.fetchData())
}
.stateIn(
    scope = viewModelScope,
    started = SharingStarted.WhileSubscribed(5_000),
    initialValue = Result.Loading
)

Используйте collectAsStateWithLifecycle для преобразования собранных значений в состояние Compose State , чтобы ваш пользовательский интерфейс мог реактивно обновляться при изменении данных.

Для получения дополнительной информации о состоянии см. раздел «Состояние и Jetpack Compose» .

Дополнительные ресурсы

Просмотры контента

Образцы

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