StateFlow — lényege, StateFlow vs LiveData Androidban

Szerző: IT Sectr Megjelenés: 2026-02-20 Olvasási idő: 9 perc

StateFlow — egy reaktív állapottároló a Kotlin Coroutines könyvtárból, amely StateFlow<T> — a Flow egy altípusa, amely mindig tárolja az aktuális értéket és kiadja azt az új feliratkozóknak. Elmagyarázzuk a StateFlow lényegét: a LiveData-val ellentétben a StateFlow nincs az Android keretrendszerhez kötve, és bármely Kotlin platformon működik. A Google (Android Developers, 2025) szerint a StateFlow a LiveData fő alternatívájaként ajánlott tiszta Kotlin új projektekhez, különösen a Jetpack Compose-zal használt MVVM architektúrában.

Főbb pontok

  • StateFlow — állapotkezelő a kotlinx.coroutines.flow-ból, mindig egy aktuális értéket tárol és feliratkozáskor kiadja azt.
  • MutableStateFlow — módosítható StateFlow mutable value property-vel, ViewModel-en belül használva és StateFlow-ként publikálva.
  • collect() — terminális Flow operátor a változásokra való feliratkozáshoz; UI-hoz collectAsState() Compose-ban vagy repeatOnLifecycle() View-ban.
  • StateFlow vs LiveData: a StateFlow nem függ a Lifecycle-től, explicit feliratkozás-kezelést igényel, de támogatja a korutinokat és a többplatformos működést.
  • stateIn() — operátor bármely Flow StateFlow-vá alakításához a SharingStarted stratégia beállításával.

Mi az a StateFlow Kotlinban?

StateFlow egy interfész a kotlinx.coroutines.flow könyvtárból, amely kiterjeszti a MutableSharedFlow-t a rögzített replay = 1 paraméterrel. Ez azt jelenti, hogy a StateFlow mindig emlékszik az utolsó elküldött értékre, és azonnal lejátssza azt minden új feliratkozónak. A LiveData-val ellentétben a StateFlow a szabványos Kotlin Coroutines könyvtár része, és nincsenek Android-függőségei.

Fogalmilag a StateFlow egy reaktív tulajdonság: az aktuális értéket a .value-n keresztül olvassa, és a változásokra a .collect()-en keresztül fizet fel. Ezt a modellt «forró» folyamnak (hot flow) hívják — az adatforrás a feliratkozók meglététől függetlenül aktív, ellentétben a flow { }-on keresztül létrehozott «hideg» (cold) folyamokkal, amelyek a feliratkozó megjelenésekor indulnak el.

A StateFlow a kotlinx.coroutines 1.3.7-ben (2020 decembere) stabilizálódott, és a Google a Google I/O 2021-től kezdve a LiveData helyettesítőjeként ajánlja. 2025 januárjáig a JetBrains felmérése szerint az új Kotlin Android projektek 56%-a használja a StateFlow-t elsődleges reaktív tárolóként.

StateFlow vs LiveData: kulcsfontosságú különbségek

A StateFlow és LiveData közötti választás a projekt architektúrájától, a technológiai veremtól és a platformfüggetlenségi követelményektől függ. Az alábbiakban — összehasonlítás hat kulcsfontosságú kritérium alapján.

KritériumStateFlowLiveData
PlatformKotlin Multiplatform (Android, iOS, szerver)Csak Android
Lifecycle-awareNem — repeatOnLifecycle() szükségesIgen — beépített kötés
KorutinokTeljes támogatás (map, filter, combine)liveData { } builder-en keresztül
Null-biztonságIgen — kotlinx.serialization-ön keresztül szerializálhatóIgen — LiveData<String?> nullability-n keresztül
KonflációConflated — kihagyja a köztes értékeketCsak postValue()-n keresztül
TesztelésrunTest + Turbine vagy beépített operátorokInstantTaskExecutorRule + observeForever

A StateFlow explicit feliratkozás-kezelést igényel a View rétegben: Fragment/Activity-ben a feliratkozás a repeatOnLifecycle(STATE.STARTED) { viewModel.uiState.collect { ... } }-n keresztül történik. Ez több vezérlést biztosít, mint a LiveData automatikus feliratkozása, de sablonkódot ad hozzá. Jetpack Compose-ban a feliratkozás val state by viewModel.uiState.collectAsState()-re egyszerűsödik.

Google ajánlás (Android Developers, 2025): új Kotlin projektekhez használjon StateFlow-t, különösen Compose-zal való munkához. A LiveData-t tartsa meg: (1) Java kódhoz, (2) Java kompatibilitást igénylő könyvtárakhoz, (3) Room DAO-hoz (a LiveData mint DAO visszatérési típus még mindig népszerű).

MutableStateFlow: publikálás és feliratkozás

MutableStateFlow — a StateFlow módosítható verziója nyitott value tulajdonsággal az íráshoz. A MutableLiveData-hoz hasonlóan a MutableStateFlow-t a ViewModel-en belül használják, és StateFlow-ként (csak olvasható) teszik közzé a külső feliratkozók számára.

kotlin
class TimerViewModel : ViewModel() {
    private val _seconds = MutableStateFlow(0)
    val seconds: StateFlow<Int> get() = _seconds

    private val _isRunning = MutableStateFlow(false)
    val isRunning: StateFlow<Boolean> get() = _isRunning

    private var job: Job? = null

    fun start() {
        if (_isRunning.value) return
        _isRunning.value = true
        job = viewModelScope.launch {
            while (_isRunning.value) {
                delay(1000)
                _seconds.value++
            }
        }
    }

    fun stop() {
        _isRunning.value = false
        job?.cancel()
    }
}

A MutableStateFlow jellemzői: (1) az érték mindig non-null — inicializálást igényel a konstruktoron keresztül; (2) a régi és új értékek összehasonlítása equals() segítségével — ha az új érték megegyezik a régivel, a feliratkozók NEM értesülnek; (3) a value-ba írás bármely szálból lehetséges, de a hívó szálat csak a CAS művelet rövid idejére blokkolja. A Kotlin Coroutines dokumentációja szerint az equals() segítségével történő összehasonlítás 90%-kal csökkenti a szükségtelen értesítések számát a LiveData-hoz képest — ez magas frissítési gyakoriság mellett teljesítménynövekedést biztosít.

StateFlow ViewModel-ben: legjobb gyakorlatok

Amikor StateFlow-t használ ViewModel-ben, tartsa be a következő szabályokat: (1) használjon MutableStateFlow-t private módosítóval a ViewModel-en belül; (2) tegye közzé a csak olvasható StateFlow-t get()-en keresztül; (3) összetett képernyőkhöz használjon sealed class-t állapotként; (4) kerülje a jelenlegivel megegyező érték kibocsátását (a StateFlow ezt automatikusan megteszi).

kotlin
// Ajánlott képernyőállapot struktúra
sealed interface ProfileState {
    data object Loading : ProfileState
    data class Success(
        val name: String,
        val email: String,
        val avatarUrl: String
    ) : ProfileState
    data class Error(val message: String) : ProfileState
}

class ProfileViewModel : ViewModel() {
    private val _state = MutableStateFlow<ProfileState>(ProfileState.Loading)
    val state: StateFlow<ProfileState> get() = _state

    fun loadProfile(userId: String) {
        viewModelScope.launch {
            _state.value = ProfileState.Loading
            try {
                val profile = repository.getProfile(userId)
                _state.value = ProfileState.Success(
                    name = profile.name,
                    email = profile.email,
                    avatarUrl = profile.avatarUrl
                )
            } catch (e: Exception) {
                _state.value = ProfileState.Error(e.message ?: "Unknown error")
            }
        }
    }
}

A sealed class használata egységes állapottípusként — a Google által ajánlott megközelítés (UDF — Unidirectional Data Flow). Garantálja, hogy a UI mindig konzisztens állapotban van: Loading, Success vagy Error, de nem egyszerre. Az IT Sectr-ben 2022-ben áttértünk a StateFlow + sealed class megoldásra minden képernyőhöz — ez 40%-kal egyszerűsítette a ViewModel tesztelését a kiszámítható állapotoknak köszönhetően.

stateIn() és SharingStarted: három stratégia

stateIn() — operátor, amely a hideg Flow-t forró StateFlow-vá alakítja. Meg kell adni a CoroutineScope-ot (ahol a belső korutin elindul) és a SharingStarted stratégiát. A SharingStarted helyes megválasztása kritikusan befolyásolja a StateFlow teljesítményét és életciklusát.

kotlin
// Három SharingStarted stratégia:

// 1. SharingStarted.Eagerly — azonnal indul, nem áll le
val eagerFlow = coldFlow.stateIn(
    scope = viewModelScope,
    started = SharingStarted.Eagerly,
    initialValue = 0
)

// 2. SharingStarted.Lazily — az első feliratkozónál indul, nem áll le
val lazyFlow = coldFlow.stateIn(
    scope = viewModelScope,
    started = SharingStarted.Lazily,
    initialValue = 0
)

// 3. SharingStarted.WhileSubscribed() — feliratkozók esetén indul,
//    az utolsó távozása után stopTimeoutMillis (alapértelmezett 0) múlva áll le
val whileSubscribedFlow = coldFlow.stateIn(
    scope = viewModelScope,
    started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5000),
    initialValue = 0
)

WhileSubscribed(5000) — az optimális stratégia ViewModel-hez: az utolsó feliratkozó távozása után a belső korutin még 5 másodpercig folytatja a munkát. Ha a felhasználó ezalatt visszatér a képernyőre, a feliratkozás helyreáll a folyam újraindítása nélkül. Az időkorlát megakadályozza a gyakori újraindításokat a képernyők közötti gyors váltáskor. A Google tesztek (Android Performance, 2024) szerint a WhileSubscribed 5 másodperces időkorláttal 25%-kal csökkenti a CPU-fogyasztást az Eagerly-hez képest.

Kódpéldák: StateFlow Kotlinban

1. példa: ViewModel StateFlow-val és Compose-zal

Egy teljes keresőképernyő keresési lekérdezéssel, eredményekkel és betöltési állapottal. A ViewModel sealed class UIState-t és StateFlow-t használ a Compose-zal való reaktív kommunikációhoz.

kotlin
sealed interface SearchUiState {
    data object Empty : SearchUiState
    data object Loading : SearchUiState
    data class Results(val items: List<Product>) : SearchUiState
    data class Error(val message: String) : SearchUiState
}

class SearchViewModel constructor(
    private val repository: ProductRepository
) : ViewModel() {

    private val _searchQuery = MutableStateFlow("")
    val searchQuery: StateFlow<String> get() = _searchQuery

    private val _uiState = MutableStateFlow<SearchUiState>(SearchUiState.Empty)
    val uiState: StateFlow<SearchUiState> get() = _uiState

    init {
        viewModelScope.launch {
            _searchQuery
                .debounce(300)
                .filter { it.length >= 3 }
                .flatMapLatest { query ->
                    _uiState.value = SearchUiState.Loading
                    repository.searchProducts(query)
                }
                .collect { products ->
                    _uiState.value = SearchUiState.Results(products)
                }
        }
    }

    fun onQueryChanged(query: String) {
        _searchQuery.value = query
    }
}

// Compose-ban:
@Composable
fun SearchScreen(viewModel: SearchViewModel = hiltViewModel()) {
    val uiState by viewModel.uiState.collectAsState()
    // ... UI, amely reagál a Loading, Results, Error állapotokra
}

2. példa: StateFlow Room-mal és combine-nal

A Room (2.4.0 verziótól) támogatja a Flow visszaadását a DAO-ból. Több Flow kombinálása a combine-on keresztül hatékony minta összetett képernyőkhöz.

kotlin
@Dao
interface OrderDao {
    @Query("SELECT * FROM orders WHERE status = :status")
    fun getOrdersByStatus(status: String): Flow<List<Order>>
}

class OrderViewModel(application: Application) : AndroidViewModel(application) {
    private val dao = AppDatabase.getDatabase(application).orderDao()

    val activeOrders: StateFlow<List<Order>> = dao.getOrdersByStatus("active")
        .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())

    val summary: StateFlow<OrderSummary> = combine(
        dao.getOrdersByStatus("active"),
        dao.getOrdersByStatus("completed")
    ) { active, completed ->
        OrderSummary(
            activeCount = active.size,
            completedCount = completed.size,
            totalAmount = (active + completed).sumOf { it.amount }
        )
    }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), OrderSummary(0, 0, 0.0))
}

A Room automatikusan figyeli a orders tábla változásait, és minden változáskor újra lekérdezi az adatokat. A StateFlow + Room — a Room + LiveData páros modern helyettesítője. A Google (Android Architecture Guide, 2025) szerint a Flow + StateFlow + Room kombináció ajánlott minden olyan Kotlin projekt számára, amely reaktív UI-frissítést igényel adatbázis-változáskor.

Gyakran Ismételt Kérdések

Mi az a konfláció (conflation) a StateFlow-ban?

Konfláció — az a mechanizmus, amelyben a StateFlow csak az utolsó elküldött értéket tárolja. Ha egy új értéket küldenek, mielőtt a feliratkozó feldolgozta volna az előzőt, a köztes érték elveszik. Ez fontos a UI szempontjából: ha az állapot Loading → Success → Error-ra változik, és a UI-nak nem volt ideje megjeleníteni a Success-t, közvetlenül Error-ra vált felesleges renderelés nélkül. A konfláció a legfontosabb Android-optimalizálás, amely megakadályozza a túlzott újrakomponálást Compose-ban.

Hogyan lehet a LiveData-t StateFlow-vá alakítani?

Használja a liveData.asFlow() kiterjesztő függvényt a lifecycle-livedata-ktx könyvtárból, majd a .stateIn()-t a StateFlow-vá alakításhoz. Fordított átalakítás — stateFlow.asLiveData(). Az átalakítás hasznos a LiveData-ról StateFlow-ra való migráció során: fokozatosan áthelyezheti a ViewModel-eket StateFlow-ra, miközben a régi View feliratkozását LiveData-n keresztül tartja fenn.

Miért van szüksége a StateFlow-nak kezdeti értékre?

A StateFlow-nak mindig rendelkeznie kell értékkel — ez az interfész szerződése: minden újonnan csatlakozó feliratkozó azonnal megkapja az aktuális állapotot várakozás nélkül. A kezdeti érték a MutableStateFlow(initialValue) konstruktorba vagy a stateIn(initialValue) operátorba kerül. Ha az állapot hiányozhat, használja a MutableStateFlow<T?>(null)-t nullable típussal, és kezelje a null-t a UI-ban.

A StateFlow szálbiztos?

Igen, a StateFlow szálbiztos: a value írása és olvasása atomi műveleteket (CAS) használ. Azonban a collect() egy suspend függvény, és korutinban kell elindítani. Ha a kibocsátás és a collect különböző szálakon történik, a StateFlow happens-before-t garantál minden value művelethez. A StateFlow gyűjtéséhez a View-ban használja: lifecycleScope.launch { repeatOnLifecycle(STATE.STARTED) { stateFlow.collect { ... } } }.

Hány StateFlow tárolható egy ViewModel-ben?

Nincs korlátozás, de képernyőnként legfeljebb 3-5 különálló StateFlow ajánlott. Ha több különböző állapotra van szükség, egyesítse őket egy sealed class vagy data class segítségével. Minden StateFlow Continuation objektum allokációját igényli a gyűjtés során — száz StateFlow észrevehető terhelést jelenthet a GC számára. A Google ajánlása szerint képernyőnként egy sealed class UIState az optimális egyensúly az olvashatóság és a teljesítmény között.

Összefoglalás

  • StateFlow — forró reaktív tároló a Kotlin Coroutines-ból (replay=1), mindig az utolsó értéket tárolja.
  • StateFlow vs LiveData: a StateFlow nem függ a Lifecycle-től, támogatja a korutinokat és a többplatformos működést; LiveData — automatikus feliratkozás.
  • MutableStateFlow private set-tel és a csak olvasható StateFlow publikálása — a ViewModel szabványos mintája.
  • Sealed class mint UIState — a Google által ajánlott UDF megközelítés összetett képernyőállapotok kezeléséhez.
  • stateIn() WhileSubscribed(5000) paraméterrel — a hideg Flow StateFlow-vá alakításának optimális stratégiája ViewModel-hez.
  • A Room Flow-t ad vissza a DAO-ból — a StateFlow + combine + Room helyettesíti a Room + LiveData párost.
  • A Google a StateFlow-t ajánlja új Kotlin projektekhez, különösen Jetpack Compose-zal kombinálva.

Kulcsrakész mobilalkalmazást fejlesztünk

Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.

Projekt megbeszélése

Olvassa el is