← Назад к вопросам

В каких случаях перезапускается LaunchedEffect

2.0 Middle🔥 131 комментариев
#UI и вёрстка#Жизненный цикл и навигация

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

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)
DisposableEffectListeners, 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 перезапускается когда меняется хотя бы один его ключ. Это механизм синхронизации между изменениями состояния и асинхронными операциями. Правильное использование ключей — критично для избежания утечек памяти и лишних запросов. Всегда явно указывай, на что должна реагировать корутина.