В предыдущей статье корутины запускались по нажатию на кнопку из обработчика события 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:
Еще один пример - изменение состояния компонента:
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 также предоставляет компонент SideEffect. В отличие от LaunchedEffect, корутина в SideEffect выполняется после завершения
композиции родительского компонента. Кроме того SideEffect не принимает параметрjd и перезапускается при каждой перекомпозиции родительского компонента.