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
collectAsState() Compose-ban vagy repeatOnLifecycle() View-ban.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.
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érium | StateFlow | LiveData |
|---|---|---|
| Platform | Kotlin Multiplatform (Android, iOS, szerver) | Csak Android |
| Lifecycle-aware | Nem — repeatOnLifecycle() szükséges | Igen — beépített kötés |
| Korutinok | Teljes támogatás (map, filter, combine) | liveData { } builder-en keresztül |
| Null-biztonság | Igen — kotlinx.serialization-ön keresztül szerializálható | Igen — LiveData<String?> nullability-n keresztül |
| Konfláció | Conflated — kihagyja a köztes értékeket | Csak postValue()-n keresztül |
| Tesztelés | runTest + Turbine vagy beépített operátorok | InstantTaskExecutorRule + 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 — 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.
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.
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).
// 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() — 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.
// 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.
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.
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
}
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.
@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
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.
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.
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.
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 { ... } } }.
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
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.
Olvassa el is