В каких случаях перезапускается LaunchedEffect
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
LaunchedEffect в Jetpack Compose
LaunchedEffect — это Composable функция, которая запускает корутину в контексте композиции. Она автоматически отменяет корутину при выходе из композиции. Понимание когда и почему она перезапускается критически важно для корректной работы с асинхронными операциями в Compose.
Основное назначение
LaunchedEffect используется для:
- Выполнения асинхронных операций (загрузка данных, API запросы)
- Обработки побочных эффектов
- Выполнения действий при изменении зависимостей
- Отмены корутин при уничтожении composable
Синтаксис
LaunchedEffect(key: Any?) {
// Код корутины запускается здесь
// Это suspend функция — можешь использовать delay(), repository.load() и т.д.
}
Когда LaunchedEffect ПЕРЕЗАПУСКАЕТСЯ
1. Когда изменяется ключ (key)
Это главное правило. LaunchedEffect отслеживает ключи параметры. При их изменении:
- Текущая корутина отменяется (cancellation)
- Запускается новая корутина с новыми параметрами
@Composable
fun UserDetailScreen(userId: String) {
var user by remember { mutableStateOf<User?>(null) }
var isLoading by remember { mutableStateOf(false) }
// LaunchedEffect перезапустится если userId изменится
LaunchedEffect(userId) { // userId — ключ
isLoading = true
user = userRepository.getUser(userId) // загружаем для конкретного userId
isLoading = false
}
// При переходе на другого пользователя (userId = "123" → "456"):
// 1. Текущая корутина отменяется
// 2. Запускается новая с userId = "456"
}
2. Множественные ключи — ANY переменится → перезапуск
@Composable
fun ProductScreen(categoryId: String, sortBy: String) {
var products by remember { mutableStateOf<List<Product>>(emptyList()) }
// LaunchedEffect перезапустится если categoryId ИЛИ sortBy изменится
LaunchedEffect(categoryId, sortBy) {
products = productRepository.getProducts(categoryId, sortBy)
}
// Перезапуск происходит при:
// - categoryId: "electronics" → "books"
// - sortBy: "price" → "rating"
// - Любом из двух одновременно
}
3. LaunchedEffect без ключей — запускается ДЕ ФАКТО один раз (антипаттерн)
// ❌ Плохо - не ясно когда перезапустится
LaunchedEffect(Unit) {
// Запускается один раз при входе в композицию
}
// ✅ Лучше - явно указать, что один раз
LaunchedEffect(Unit) {
// Явно один раз, Unit не меняется
}
Сценарии перезапуска
Сценарий 1: Загрузка данных при изменении ID
@Composable
fun ArticleDetailScreen(articleId: String) {
var content by remember { mutableStateOf("") }
var isLoading by remember { mutableStateOf(false) }
LaunchedEffect(articleId) { // Перезапустится при изменении articleId
isLoading = true
try {
content = articleRepository.fetchArticle(articleId)
} finally {
isLoading = false
}
}
// При articleId: "1" → "2" → "3"
// Корутина каждый раз начинается заново
// Предыдущие запросы отменяются
}
Сценарий 2: Инициализация при монтировании (один раз)
@Composable
fun HomeScreen() {
var todos by remember { mutableStateOf<List<Todo>>(emptyList()) }
// Unit не меняется — фактически один раз
LaunchedEffect(Unit) {
todos = todoRepository.getAllTodos()
}
// При каждой перекомпозиции (но не при перезапуске экрана)
// корутина НЕ запускается заново
}
Сценарий 3: Таймер с переключением
@Composable
fun TimerScreen(isActive: Boolean) {
var seconds by remember { mutableStateOf(0) }
LaunchedEffect(isActive) { // Перезапустится при isActive: true ↔ false
while (isActive) {
delay(1000)
seconds++
}
}
// Когда isActive = true: начинает отчёт
// Когда isActive = false: отменяет корутину, счётчик замораживается
}
Сценарий 4: Запрос с несколькими параметрами
@Composable
fun SearchScreen(query: String, filters: FilterOptions) {
var results by remember { mutableStateOf<List<Item>>(emptyList()) }
LaunchedEffect(query, filters) { // Перезапустится если query ИЛИ filters изменятся
if (query.isNotEmpty()) {
results = searchRepository.search(query, filters)
}
}
// Перезапуск при:
// - Вводе текста в search
// - Изменении фильтров
}
LaunchedEffect vs outras Effect'ы
| Effect | Для чего | Отмена при выходе |
|---|---|---|
| LaunchedEffect | Асинхронные операции, корутины | Да (отменяет suspend) |
| DisposableEffect | Listeners, subscriptions | Да (вызывает onDispose) |
| SideEffect | Синхронный код, state обновления | Нет |
| snapshotFlow | Отслеживание State<T> | Да (отменяет Flow) |
Best Practices
✅ Явно указывай ключи — делает ясным когда перезапустится
// ✅ Ясно
LaunchedEffect(userId) { ... }
// ❌ Непонятно
LaunchedEffect(Unit) { ... } // или вообще без ключа
✅ Используй try-finally для очистки ресурсов
LaunchedEffect(key) {
try {
// основной код
} finally {
// очистка (отмена подписок, закрытие соединений)
}
}
✅ Отмени длительные операции при смене ключа
LaunchedEffect(movieId) {
// Если пользователь переключится на другой фильм
// текущий запрос будет отменён автоматически
val movie = movieRepository.getMovie(movieId)
updateUI(movie)
}
❌ Не используй без ключей без явного понимания
// ❌ Может привести к бесконечным запускам
LaunchedEffect {
loadData()
}
Типичная ошибка
// ❌ Перезапускается при каждой перекомпозиции!
var key = userId // меняется каждый раз
LaunchedEffect(key) {
loadData()
}
// ✅ Правильно
LaunchedEffect(userId) { // используй существующее значение
loadData()
}
Визуальный поток
1. Composable входит в композицию
↓
2. LaunchedEffect проверяет ключи
↓
3. Если ключи новые или изменились:
→ Отменяет старую корутину (если была)
→ Запускает новую корутину
↓
4. Корутина выполняется (асинхронно)
↓
5. При выходе из композиции:
→ Корутина отменяется автоматически
Заключение
LaunchedEffect перезапускается когда меняется хотя бы один его ключ. Это механизм синхронизации между изменениями состояния и асинхронными операциями. Правильное использование ключей — критично для избежания утечек памяти и лишних запросов. Всегда явно указывай, на что должна реагировать корутина.