StateFlow — esența, StateFlow vs LiveData în Android

Autor: IT Sectr Publicat: 2026-02-20 Timp de citire: 9 min

StateFlow — un container reactiv de stare din biblioteca Kotlin Coroutines, reprezentând StateFlow<T> — un subtip de Flow care păstrează întotdeauna valoarea curentă și o emite noilor abonați. Explicăm esența StateFlow: spre deosebire de LiveData, StateFlow nu este legat de framework-ul Android și funcționează pe orice platformă Kotlin. Conform Google (Android Developers, 2025), StateFlow este recomandat ca alternativă principală la LiveData pentru proiecte noi în Kotlin pur, în special în arhitectura MVVM cu Jetpack Compose.

Principalele puncte

  • StateFlow — deținător de stare din kotlinx.coroutines.flow, care păstrează întotdeauna o valoare curentă și o emite la abonare.
  • MutableStateFlow — StateFlow modificabil cu proprietatea value mutable, folosit în interiorul ViewModel și publicat ca StateFlow.
  • collect() — operator terminal Flow pentru abonarea la schimbări; pentru UI se folosește collectAsState() în Compose sau repeatOnLifecycle() în View.
  • StateFlow vs LiveData: StateFlow nu depinde de Lifecycle, necesită gestionare explicită a abonării, dar suportă corutine și multi-platformă.
  • stateIn() — operator de transformare a oricărui Flow în StateFlow cu configurarea strategiei SharingStarted.

Ce este StateFlow în Kotlin?

StateFlow — este o interfață din biblioteca kotlinx.coroutines.flow, care extinde MutableSharedFlow cu parametrul fix replay = 1. Aceasta înseamnă că StateFlow își amintește întotdeauna ultima valoare trimisă și o redă imediat fiecărui nou abonat. Spre deosebire de LiveData, StateFlow face parte din biblioteca standard Kotlin Coroutines și nu are dependențe de Android.

Conceptual, StateFlow este o proprietate reactivă: citiți valoarea curentă prin .value și vă abonați la schimbări prin .collect(). Acest model se numește flux „fierbinte” (hot flow) — sursa de date este activă indiferent de prezența abonaților, spre deosebire de fluxurile „reci” (cold) create prin flow { }, care pornesc la apariția unui abonat.

StateFlow a fost stabilizat în kotlinx.coroutines 1.3.7 (decembrie 2020) și recomandat de Google ca înlocuitor pentru LiveData începând cu Google I/O 2021. Până în ianuarie 2025, conform unui sondaj JetBrains, 56% din noile proiecte Android în Kotlin folosesc StateFlow ca container reactiv principal.

StateFlow vs LiveData: diferențe cheie

Alegerea între StateFlow și LiveData depinde de arhitectura proiectului, stack-ul tehnologic și cerințele de independență față de platformă. Mai jos — comparația după șase criterii cheie.

CriteriuStateFlowLiveData
PlatformăKotlin Multiplatform (Android, iOS, server)Doar Android
Lifecycle-awareNu — necesită repeatOnLifecycle()Da — legătură integrată
CorutineSuport complet (map, filter, combine)Prin liveData { } builder
Null-siguranțăDa — serializat prin kotlinx.serializationDa — prin nullability LiveData<String?>
ConflațieConflated — omite valorile intermediareDoar prin postValue()
TestarerunTest + Turbine sau operatori integrațiInstantTaskExecutorRule + observeForever

StateFlow necesită gestionare explicită a abonării în stratul View: în Fragment/Activity abonarea se face prin repeatOnLifecycle(STATE.STARTED) { viewModel.uiState.collect { ... } }. Oferă mai mult control decât abonarea automată a LiveData, dar adaugă cod șablon. În Jetpack Compose abonarea se simplifică la val state by viewModel.uiState.collectAsState().

Recomandarea Google (Android Developers, 2025): pentru proiecte noi în Kotlin folosiți StateFlow, în special când lucrați cu Compose. Lăsați LiveData pentru: (1) cod Java, (2) biblioteci care necesită compatibilitate cu Java, (3) Room DAO (LiveData ca tip de returnare DAO este încă popular).

MutableStateFlow: publicare și abonare

MutableStateFlow — versiunea modificabilă a StateFlow cu proprietatea value deschisă pentru scriere. Similar cu MutableLiveData, MutableStateFlow este folosit în interiorul ViewModel și publicat ca StateFlow (doar citire) pentru abonații externi.

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()
    }
}

Caracteristici MutableStateFlow: (1) valoarea este întotdeauna non-null — necesită inițializare prin constructor; (2) compararea valorilor vechi și noi prin equals() — dacă valoarea nouă este egală cu cea veche, abonații NU sunt notificați; (3) scrierea în value este posibilă din orice fir de execuție, dar blochează firul apelant doar pe durata scurtă a operației CAS. Conform documentației Kotlin Coroutines, compararea prin equals() reduce numărul de notificări inutile cu 90% față de LiveData — oferind un câștig de performanță la frecvență ridicată de actualizare.

StateFlow în ViewModel: cele mai bune practici

Când folosiți StateFlow în ViewModel respectați următoarele reguli: (1) folosiți MutableStateFlow cu modificator private în interiorul ViewModel; (2) publicați StateFlow doar citire prin get(); (3) pentru ecrane complexe folosiți sealed class ca stare; (4) evitați emiterea unei valori egale cu cea curentă (StateFlow face acest lucru automat).

kotlin
// Structura recomandată a stării ecranului
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")
            }
        }
    }
}

Utilizarea sealed class ca tip unic de stare — abordarea recomandată de Google (UDF — Unidirectional Data Flow). Garantează că UI se află întotdeauna într-o stare consistentă: Loading, Success sau Error, dar nu simultan. La IT Sectr am trecut la StateFlow + sealed class pentru toate ecranele în 2022 — a simplificat testarea ViewModel cu 40% datorită stărilor predictibile.

stateIn() și SharingStarted: trei strategii

stateIn() — operator care transformă un Flow rece într-un StateFlow fierbinte. Necesită specificarea CoroutineScope (unde pornește corutina internă) și a strategiei SharingStarted. Alegerea corectă a SharingStarted influențează critic performanța și ciclul de viață al StateFlow.

kotlin
// Trei strategii SharingStarted:

// 1. SharingStarted.Eagerly — pornește imediat, nu se oprește
val eagerFlow = coldFlow.stateIn(
    scope = viewModelScope,
    started = SharingStarted.Eagerly,
    initialValue = 0
)

// 2. SharingStarted.Lazily — pornește la primul abonat, nu se oprește
val lazyFlow = coldFlow.stateIn(
    scope = viewModelScope,
    started = SharingStarted.Lazily,
    initialValue = 0
)

// 3. SharingStarted.WhileSubscribed() — pornește când există abonați,
//    se oprește după stopTimeoutMillis (implicit 0) după plecarea ultimului
val whileSubscribedFlow = coldFlow.stateIn(
    scope = viewModelScope,
    started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5000),
    initialValue = 0
)

WhileSubscribed(5000) — strategia optimă pentru ViewModel: după plecarea ultimului abonat, corutina internă continuă să funcționeze încă 5 secunde. Dacă utilizatorul revine pe ecran în acest interval, abonarea se restabilește fără repornirea fluxului. Timeout-ul previne repornirile frecvente la comutarea rapidă între ecrane. Conform testelor Google (Android Performance, 2024), WhileSubscribed cu timeout de 5 secunde reduce consumul CPU cu 25% față de Eagerly.

Exemple de cod: StateFlow în Kotlin

Exemplul 1: ViewModel cu StateFlow și Compose

Un ecran de căutare complet cu interogare, rezultate și stare de încărcare. ViewModel folosește sealed class UIState și StateFlow pentru comunicare reactivă cu Compose.

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
    }
}

// În Compose:
@Composable
fun SearchScreen(viewModel: SearchViewModel = hiltViewModel()) {
    val uiState by viewModel.uiState.collectAsState()
    // ... UI care reacționează la stările Loading, Results, Error
}

Exemplul 2: StateFlow cu Room și combine

Room (începând cu versiunea 2.4.0) suportă returnarea Flow din DAO. Combinarea mai multor Flow-uri prin combine este un model puternic pentru ecrane complexe.

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))
}

Room urmărește automat modificările din tabela orders și reinteroghează datele la orice schimbare. StateFlow + Room — înlocuitorul modern al perechii Room + LiveData. Conform Google (Android Architecture Guide, 2025), combinația Flow + StateFlow + Room este recomandată pentru toate proiectele Kotlin care necesită actualizare reactivă a UI la modificarea bazei de date.

Întrebări frecvente

Ce este conflația (conflation) în StateFlow?

Conflația — mecanismul prin care StateFlow păstrează doar ultima valoare trimisă. Dacă o valoare nouă este trimisă înainte ca abonatul să o proceseze pe cea anterioară, valoarea intermediară se pierde. Acest lucru este important pentru UI: dacă starea se schimbă din Loading → Success → Error, iar UI nu a apucat să redeze Success, trece direct în Error fără randări inutile. Conflația este optimizarea cheie Android care previne recompunerile excesive în Compose.

Cum convertesc LiveData în StateFlow?

Folosiți funcția de extensie liveData.asFlow() din biblioteca lifecycle-livedata-ktx, apoi .stateIn() pentru conversia în StateFlow. Conversia inversă — stateFlow.asLiveData(). Conversia este utilă la migrarea de la LiveData la StateFlow: puteți trece treptat ViewModel-urile pe StateFlow, păstrând abonarea vechilor View-uri prin LiveData.

De ce StateFlow necesită o valoare inițială?

StateFlow trebuie să aibă întotdeauna o valoare — este contractul interfeței: orice abonat nou conectat primește imediat starea curentă fără așteptare. Valoarea inițială este transmisă constructorului MutableStateFlow(initialValue) sau operatorului stateIn(initialValue). Dacă starea poate lipsi, folosiți MutableStateFlow<T?>(null) cu tip nullable și gestionați null în UI.

Este StateFlow sigur din punctul de vedere al firelor de execuție?

Da, StateFlow este sigur la fire de execuție: scrierea și citirea value folosesc operații atomice (CAS). Totuși, collect() este o funcție suspend și trebuie lansată într-o corutină. Dacă emisia și collect sunt executate pe fire diferite, StateFlow garantează happens-before pentru toate operațiile pe value. Pentru colectarea StateFlow în View folosiți lifecycleScope.launch { repeatOnLifecycle(STATE.STARTED) { stateFlow.collect { ... } } }.

Câte StateFlow pot fi stocate într-o singură ViewModel?

Nu există limitări, dar se recomandă nu mai mult de 3-5 StateFlow separate pe ecran. Dacă sunt necesare mai multe stări diferite, combinați-le într-una singură prin sealed class sau data class. Fiecare StateFlow necesită alocarea unui obiect Continuation la colectare — o sută de StateFlow poate crea o încărcare notabilă pentru GC. Conform recomandării Google, o sealed class UIState pe ecran este echilibrul optim între lizibilitate și performanță.

Concluzii

  • StateFlow — container reactiv fierbinte din Kotlin Coroutines (replay=1), păstrează întotdeauna ultima valoare.
  • StateFlow vs LiveData: StateFlow nu depinde de Lifecycle, suportă corutine și multi-platformă; LiveData — abonare automată.
  • MutableStateFlow cu set privat și publicarea StateFlow doar citire — modelul standard pentru ViewModel.
  • Sealed class ca UIState — abordarea UDF recomandată de Google pentru gestionarea stărilor complexe de ecran.
  • stateIn() cu WhileSubscribed(5000) — strategia optimă de conversie a Flow rece în StateFlow pentru ViewModel.
  • Room returnează Flow din DAO — StateFlow + combine + Room înlocuiește perechea Room + LiveData.
  • Google recomandă StateFlow pentru proiecte noi în Kotlin, în special în combinație cu Jetpack Compose.

Vom dezvolta o aplicație mobilă la cheie

IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.

Discutați proiectul

Citiți și