LaunchedEffect

Последнее обновление: 08.04.2024

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

suspend fun doWork() {
    println("doWork starts")
    delay(5000)
    println("doWork ends")
}

@Composable
fun HelloWork(n) {
    val coroutineScope = rememberCoroutineScope()
    coroutineScope.launch() {
        doWork()
    }
}

При попытке скомпилировать этот код мы получим ошибку

Calls to launch should happen inside a LaunchedEffect and not composition

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

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

Общий синтаксис запуска корутин из LaunchedEffect выглядит следующим образом:

LaunchedEffect(key1, key2, ...) {

    coroutineScope.launch() {

        // здесь выполняется асинхронный код
    }
}

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

ПРименим компонент LaunchedEffect:

package com.example.helloapp

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.Text
import androidx.compose.ui.unit.sp
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.rememberCoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent{
            HelloWork()
        }
    }
}
suspend fun doWork() {
    println("doWork starts")
    delay(5000)
    println("doWork ends")
}

@Composable
fun HelloWork() {
    val coroutineScope = rememberCoroutineScope()
    LaunchedEffect(key1 = Unit) {
        coroutineScope.launch() {
            doWork()
        }
    }
    Text("Hello Work", fontSize = 28.sp)
}

Здесь в компоненте HelloWork запускается корутина, которая выполняет функцию doWork. Эта функция для демонстрации просто логгирует на консоль две строки с задержкой в 5 секунд. Для запуска корутины определен компонент LaunchedEffect. Обратите внимание, что в функцию этого компонента в качестве ключа передается значение Unit. Это значение указывает, что корутину не нужно воссоздавать в течение рекомпозиций. В остальном работа с корутиной протекает также. Также для простой демонстрации внутри HelloWork вызывыается компонент Text, который выводит надпись "Hello Work".

Запустим приложение, и при запуске компонента, и в окне Logcat внизу Android Studio мы сможем увидеть сообщения из функции doWork:

Корутины и асинхронность в Jetpack Compose и Kotlin на Android

Еще один пример - изменение состояния компонента:

package com.example.helloapp

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.ui.unit.sp
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent{
            Counter()
        }
    }
}

@Composable
fun Counter() {
    val count = remember{mutableStateOf(0)}
    val coroutineScope = rememberCoroutineScope()
    LaunchedEffect(key1 = Unit) {
        coroutineScope.launch() {
            for(n in 1..5){
                count.value = n
                delay(1000)
            }
        }
    }
    Text("Count: ${count.value}", Modifier.padding(start = 10.dp), fontSize = 28.sp)
}

Здесь определен компонент Counter, в котором вложенный компонент Text отображает значение состояния - переменной count. В корутине, которая запускается в LaunchedEffect, в цикле изменяем значение count с 1 до 5 с задержкой в 1 секунду. После этого выходим из цикла, и корутина завершается.

Запустим приложение, и при старте будет запущена корутина, которая будет изменять состояние переменной count:

Корутины и LaunchedEffect в Jetpack Compose и Kotlin на Android

Кроме LaunchedEffect Jetpack Compose также предоставляет компонент SideEffect. В отличие от LaunchedEffect, корутина в SideEffect выполняется после завершения композиции родительского компонента. Кроме того SideEffect не принимает параметрjd и перезапускается при каждой перекомпозиции родительского компонента.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850