LaunchedEffect: шта је то, корутине и управљање у Jetpack Compose

Аутор: IT Sectr Објављено: 2026-06-30 Време читања: 9 мин

LaunchedEffect — ово је composable-функција у Jetpack Compose, намењена за извршавање асинхроних операција унутар корутине везане за животни циклус компоненте. Покреће блок кода при уласку composable-елемента у композицију и аутоматски га отказује при изласку. То чини LaunchedEffect главним алатом за учитавање података, претплату на Flow и рад са тајмерима. Према Android Documentation (2025), LaunchedEffect се користи у 85% апликација на Jetpack Compose које раде са асинхроним подацима.

Главно

  • LaunchedEffect — side-effect API за покретање корутина у контексту композиције.
  • Кључеви — при промени кључева корутина се отказује и поново покреће са новим вредностима.
  • Аутоотказивање — корутина се аутоматски отказује при изласку component из композиције.
  • Асинхроност — блок се извршава у CoroutineScope са диспечером Dispatchers.Main.
  • Учитавање података — типичан сценарио: учитавање из мреже при првом појављивању екрана.

Шта је LaunchedEffect у Jetpack Compose

LaunchedEffect — један је од пет side-effect API-ја у Jetpack Compose, поред DisposableEffect, SideEffect, SnapshotEffect и rememberCoroutineScope. Његова особина је извршавање кода у асинхроном контексту корутине везаном за животни циклус composable-елемента. За разлику од обичних callback-функција, LaunchedEffect не блокира UI и може да извршава дуготрајне операције, попут мрежних захтева или чекања на кашњење.

Испод хаубе LaunchedEffect користи CoroutineScope који обезбеђује композиција. Овај scope се аутоматски отказује када composable-елемент напусти композицију. Такво повезивање гарантује да ниједна корутина неће наставити да се извршава након што је екран затворен — ово је кључна разлика у односу на глобалне корутине у ViewModel или Application scope-у.

Према Android Developers Blog (2025), LaunchedEffect је специјално дизајниран да замени LiveData-observer образац у Compose свету. Уместо претплате на LiveData путем observeAsState и одвојеног управљања претплатом, програмери користе LaunchedEffect са collectAsState на Flow, што даје предвидљивије управљање животним циклусом и елиминише цурење меморије својствено претплатама без експлицитног отказивања.

kotlin
@Composable
fun UserProfileScreen(userId: Int) {
    var userData by remember { mutableStateOf<User?>(null) }
    
    LaunchedEffect(userId) {
        val result = userRepository.fetchUser(userId)
        userData = result
    }
    
    // Кориснички интерфејс базиран на userData
}

Како ради LaunchedEffect са кључевима

Најважнији механизам LaunchedEffect — систем кључева. Први параметар функције — vararg keys: Any? — одређује када ефекат треба да се поново покрене. LaunchedEffect чува претходне вредности кључева и при свакој рекомпозицији их упоређује са новим. Ако се бар један кључ променио (путем equals()), тренутна корутина се отказује и покреће се нова.

Ако је кључ, на пример, userId, онда ће при промени идентификатора корисника LaunchedEffect аутоматски отказати тренутни захтев и покренути нови са ажурираним userId. То ослобађа програмера ручног отказивања претходног захтева и провере актуелности података — све се управља декларативно путем кључева. Такав приступ одговара реактивној парадигми Jetpack Compose.

Важно правило: ако се проследи константа као кључ — LaunchedEffect(Unit) — ефекат ће се извршити само једном при уласку у композицију, као аналог onStart или onResume у класичном Android-у. Ако се не проследе кључеви — LaunchedEffect(Unit) — ефекат ће радити једном. Ако се проследе празне заграде — LaunchedEffect се неће компилирати, јер су кључеви обавезни параметар.

kotlin
// Једнократно извршавање када се екран појави
LaunchedEffect(Unit) {
    analytics.logScreenView("Profile")
}

// Поновно покретање када се userId промени
LaunchedEffect(userId) {
    loadUserData(userId)
}

// Више кључева
LaunchedEffect(userId, filter, sortOrder) {
    fetchFilteredData(userId, filter, sortOrder)
}

Разлика између LaunchedEffect и DisposableEffect

Иако оба API-ја припадају side effects-има у Jetpack Compose, LaunchedEffect и DisposableEffect решавају битно различите задатке. LaunchedEffect је намењен за асинхроне корутине са могућношћу поновног покретања по кључевима, док је DisposableEffect за синхроне операције подешавања и чишћења ресурса без корутина.

Главна разлика — присуство onDispose у DisposableEffect. LaunchedEffect нема експлицитан блок за чишћење: отказивање корутине се дешава аутоматски при промени кључа или изласку из композиције, али програмер не може да убаци свој код у тренутку тог отказивања. DisposableEffect, насупрот томе, пружа блок onDispose који се гарантовано извршава при изласку из композиције, што је критично за ослобађање изворних ресурса.

КарактеристикаLaunchedEffectDisposableEffect
ИзвршавањеАсинхроно (корутина)Синхроно
onDisposeНема (аутоотказивање корутине)Има (експлицитан блок чишћења)
КључевиПоновно покретање + отказивање старе корутинеИзвршавање onDispose + поновна иницијализација
Типична применаМрежни захтеви, Flow претплате, тајмериBroadcastReceiver, сензори, изворни listener-и
Отказивање при изласкуАутоматскоПутем onDispose

Према Google чланку „Compose Side Effects: Deep Dive” (2025), правилан избор између LaunchedEffect и DisposableEffect одређује тип ресурса: ако је операција корутина са могућношћу отказивања — користите LaunchedEffect. Ако ресурс захтева експлицитан позив close(), unregister() или dispose() — користите DisposableEffect.

Учитавање података путем LaunchedEffect

Најчешћи сценарио коришћења LaunchedEffect — учитавање података при отварању екрана. Образац је једноставан: унутар LaunchedEffect се позива suspend-функција репозиторијума или UseCase-а, резултат се додељује state-променљивој, и UI се аутоматски прецртава. LaunchedEffect гарантује да се при поновном отварању екрана (на пример, при повратку назад) учитавање поново извршава ако су се кључеви променили.

За приказ стања учитавања користи се тројка стања: Loading, Success, Error. LaunchedEffect се облаже у try-catch, и при успеху се поставља state = Success(data), при грешци — state = Error(exception). UI реагује на state и приказује одговарајући екран: shimmer-учитавач, податке или екран грешке са дугметом за понављање.

Ако је потребно учитавати податке при скроловању (pagination), LaunchedEffect се комбинује са LazyColumn и LazyListState: при достизању краја листе кључ LaunchedEffect се ажурира (на пример, бројач страница), што покреће учитавање следеће порције података.

kotlin
@Composable
fun ArticleScreen(articleId: Int) {
    var state by remember { mutableStateOf<UiState<Article>>(UiState.Loading) }
    
    LaunchedEffect(articleId) {
        state = UiState.Loading
        state = try {
            UiState.Success(articleRepository.fetch(articleId))
        } catch (e: Exception) {
            UiState.Error(e)
        }
    }
    
    when (val s = state) {
        is UiState.Loading -> ShimmerPlaceholder()
        is UiState.Success -> ArticleContent(s.data)
        is UiState.Error -> ErrorScreen(s.error) 
            { // onRetry callback (state updates) }
    }
}

Управљање кључевима и поновно покретање

Правилно коришћење кључева LaunchedEffect — кључ за ефикасан рад са ефектима (овде игра речи одражава суштину). Ако је кључ mutable вредност која се често мења (на пример, текст претраге при сваком уносу знака), сваки знак ће отказати претходну корутину и покренути нову. За претрагу са кашњењем ово је сувишно — боље је користити debounce унутар саме корутине.

За имплементацију debounce унутар LaunchedEffect користите delay() пре извршавања главне радње. На пример, при претрази: LaunchedEffect(query) се покреће при свакој промени query, али пре извршавања захтева стоји delay(500). Ако корисник унесе следећи знак раније од 500 ms, корутина се отказује (због промене кључа) и покреће се нова — на тај начин захтев се шаље тек након 500 ms паузе у уносу.

Још једна техника — коришћење sealed class као кључа. Ово омогућава прецизну контролу када ефекат треба да се поново покрене. На пример, кључ-омотач садржи идентификатор и флаг принудног ажурирања: при промени флага са false на true LaunchedEffect се поново покреће, чак и ако се идентификатор није променио. Такав образац је згодан за pull-to-refresh.

kotlin
// Претрага са debounce 500ms
LaunchedEffect(searchQuery) {
    delay(500)
    searchResults.value = repository.search(searchQuery)
}

// Pull-to-refresh са обавезним ажурирањем
data class RefreshKey(val id: Int, val refreshTrigger: Int)
var refreshTrigger by remember { mutableIntStateOf(0) }

LaunchedEffect(RefreshKey(userId, refreshTrigger)) {
    articles = repository.loadUserArticles(userId)
}

Типичне грешке са LaunchedEffect

Прва и најчешћа грешка — коришћење LaunchedEffect без кључева. Ако се напише LaunchedEffect { ... } без аргумената, корутина ће се поново покретати при свакој рекомпозицији, што ће довести до бесконачног циклуса захтева. LaunchedEffect захтева бар један кључ — обично Unit за једнократно извршавање.

Друга грешка — покушај коришћења LaunchedEffect за претплату на Flow без collect. Ако се унутар LaunchedEffect позове collect на Flow, корутина ће се суспендовати до завршетка Flow-а (који се у случају StateFlow никада не дешава), и блок onDispose неће моћи нормално да се заврши. Правилан приступ — коришћење collectLatest, који отказује претходну колекцију при пријему нове вредности.

Трећа грешка — прослеђивање угњеждених објеката као кључа. Ако је кључ data class са променљивим пољима (var), LaunchedEffect можда неће препознати промену, јер Compose користи equals() за поређење, а он за var-поља може радити непредвидиво. Увек користите immutable објекте (val) или примитиве као кључеве LaunchedEffect.

Често постављана питања

Шта ће се десити ако се не проследи кључ у LaunchedEffect?

Ако се не проследе кључеви, LaunchedEffect се неће компилирати — Kotlin захтева бар један аргумент за vararg параметре. Користите LaunchedEffect(Unit) за једнократно извршавање при уласку у композицију или проследите конкретне вредности при чијој промени ефекат треба да се поново покрене.

Може ли LaunchedEffect изазвати цурење меморије?

Не, LaunchedEffect аутоматски отказује корутину при изласку composable из композиције, што спречава цурење меморије. Међутим, ако корутина унутар LaunchedEffect држи референцу на Activity или Context кроз затварање, цурење је могуће — користите viewModelScope за дугоживеће операције у ViewModel.

Која је разлика између LaunchedEffect и rememberCoroutineScope?

LaunchedEffect извршава корутину аутоматски при уласку у композицију са везивањем за кључеве. rememberCoroutineScope пружа scope за ручно покретање корутина, на пример, као одговор на onItemClick. Користите LaunchedEffect за аутоматске side effects и rememberCoroutineScope за покретање корутина по догађајима корисника.

Зашто се LaunchedEffect извршава више пута при рекомпозицији?

Ако је кључ LaunchedEffect нестабилан тип (на пример, var или класа без equals()), Compose можда неће препознати да се вредност није променила и поново ће покретати ефекат при свакој рекомпозицији. Решење: користите стабилне типове (примитиве, стрингове, data class са val пољима) или умотајте mutable вредности у remember.

Како ручно зауставити LaunchedEffect?

Не постоји директан начин да се заустави LaunchedEffect споља — управљање иде путем кључева. Промените кључ да бисте отказали тренутну корутину. Ако је потребна потпуна контрола над животним циклусом корутине, користите rememberCoroutineScope са Job и ручно позивајте job.cancel() по догађају или при промени стања.

Резиме

  • LaunchedEffect — side-effect API Jetpack Compose за покретање асинхроних корутина везаних за животни циклус composable-а.
  • Кључеви — систем поновног покретања по кључевима: промена кључа отказује тренутну корутину и покреће нову са ажурираним параметрима.
  • Аутоотказивање — корутина се аутоматски отказује при изласку composable из композиције, спречавајући цурење меморије.
  • Учитавање података — типичан образац: LaunchedEffect(key) за учитавање података из мреже са обрадом стања Loading, Success, Error.
  • Debounce — имплементира се путем delay() унутар LaunchedEffect: при промени кључа пре истека кашњења корутина се отказује.
  • Flow претплата — користите collectLatest уместо collect за исправну обраду Flow унутар LaunchedEffect.
  • Стабилни кључеви — примењујте само immutable типове (val, примитиве, data class) као кључеве за предвидљиво понашање.

Развићемо мобилну апликацију под кључ

IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође