StateFlow — StateFlow vs LiveData i Android

Författare: IT Sectr Publicerad: 2026-02-20 Lästid: 9 min

StateFlow — reaktiv tillståndsbehållare från Kotlin Coroutines-biblioteket, som är StateFlow<T> — en undertyp av Flow som alltid håller det aktuella värdet och skickar det till nya prenumeranter. Förklarar kärnan i StateFlow: till skillnad från LiveData är StateFlow inte bundet till Android-ramverket och fungerar på vilken Kotlin-plattform som helst. Enligt Google (Android Developers, 2025) rekommenderas StateFlow som det främsta alternativet till LiveData för nya projekt i ren Kotlin, särskilt i MVVM-arkitektur med Jetpack Compose.

Huvudpunkter

  • StateFlow — tillståndshållare från kotlinx.coroutines.flow, som alltid håller ett aktuellt värde och skickar det vid prenumeration.
  • MutableStateFlow — föränderlig StateFlow med mutable value property, används inuti ViewModel och publiceras som StateFlow.
  • collect() — terminaloperatör för Flow för att prenumerera på ändringar; för UI används collectAsState() i Compose eller repeatOnLifecycle() i View.
  • StateFlow vs LiveData: StateFlow är inte beroende av Lifecycle, kräver explicit prenumerationshantering, men stöder korutiner och multiplattform.
  • stateIn() — operator för att konvertera valfri Flow till StateFlow med konfiguration av SharingStarted-strategi.

Vad är StateFlow i Kotlin?

StateFlow — är ett gränssnitt från biblioteket kotlinx.coroutines.flow som utökar MutableSharedFlow med den fasta parametern replay = 1. Detta innebär att StateFlow alltid kommer ihåg det senast skickade värdet och omedelbart spelar upp det för varje ny prenumerant. Till skillnad från LiveData är StateFlow en del av standardbiblioteket Kotlin Coroutines och har inga beroenden till Android.

Konceptuellt är StateFlow en reaktiv egenskap: du läser dess aktuella värde via .value och prenumererar på ändringar via .collect(). Denna modell kallas "varm" ström (hot flow) — datakällan är aktiv oberoende av om det finns prenumeranter, till skillnad från "kalla" (cold) strömmar som skapas via flow { } och startas när en prenumerant dyker upp.

StateFlow stabiliserades i kotlinx.coroutines 1.3.7 (december 2020) och rekommenderades av Google som ersättning för LiveData från och med Google I/O 2021. I januari 2025, enligt JetBrains enkät, använder 56% av nya Android-projekt i Kotlin StateFlow som primär reaktiv behållare.

StateFlow vs LiveData: viktiga skillnader

Valet mellan StateFlow och LiveData beror på projektets arkitektur, teknologistack och krav på plattformsoberoende. Nedan följer en jämförelse enligt sex viktiga kriterier.

KriteriumStateFlowLiveData
PlattformKotlin Multiplatform (Android, iOS, server)Endast Android
Lifecycle-awareNej — kräver repeatOnLifecycle()Ja — inbyggd bindning
KorutinerFullt stöd (map, filter, combine)Via liveData { } builder
Null-säkerhetJa — serialiseras via kotlinx.serializationJa — via nullability LiveData<String?>
KonflationKonflaterad — hoppar över mellanliggande värdenEndast via postValue()
TestningrunTest + Turbine eller inbyggda operatorerInstantTaskExecutorRule + observeForever

StateFlow kräver explicit prenumerationshantering i View-lagret: i Fragment/Activity utförs prenumeration via repeatOnLifecycle(STATE.STARTED) { viewModel.uiState.collect { ... } }. Detta ger mer kontroll än LiveDatas automatiska prenumeration, men lägger till mallkod. I Jetpack Compose förenklas prenumerationen till val state by viewModel.uiState.collectAsState().

Googles rekommendation (Android Developers, 2025): för nya projekt i Kotlin, använd StateFlow, särskilt när du arbetar med Compose. Behåll LiveData för: (1) Java-kod, (2) bibliotek som kräver Java-kompatibilitet, (3) Room DAO (LiveData som DAO-returtyp är fortfarande populär).

MutableStateFlow: publicering och prenumeration

MutableStateFlow — föränderlig version av StateFlow med öppen property value för skrivning. I analogi med MutableLiveData används MutableStateFlow inuti ViewModel och publiceras som StateFlow (skrivskyddad) för externa prenumeranter.

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

Egenskaper för MutableStateFlow: (1) värdet är alltid icke-null — kräver initiering via konstruktor; (2) jämförelse av gamla och nya värden via equals() — om det nya värdet är lika med det gamla, meddelas INTE prenumeranter; (3) skrivning till value är möjlig från vilken tråd som helst, men blockerar den anropande tråden endast under den korta CAS-operationen. Enligt Kotlin Coroutines dokumentation minskar jämförelse via equals() antalet onödiga meddelanden med 90% jämfört med LiveData — detta ger prestandaökning vid hög uppdateringsfrekvens.

StateFlow i ViewModel: bästa praxis

När du använder StateFlow i ViewModel, följ dessa regler: (1) använd MutableStateFlow med private-modifierare inuti ViewModel; (2) publicera skrivskyddad StateFlow via get(); (3) för komplexa skärmar, använd sealed class som tillstånd; (4) undvik att skicka ett värde som är lika med det aktuella (StateFlow gör detta automatiskt).

kotlin
// Rekommenderad skärmtillståndsstruktur
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")
            }
        }
    }
}

Användning av sealed class som enhetlig tillståndstyp är Googles rekommenderade metod (UDF — Unidirectional Data Flow). Den garanterar att UI alltid är i ett konsekvent tillstånd: Loading, Success eller Error, men inte samtidigt. På IT Sectr bytte vi till StateFlow + sealed class för alla skärmar 2022 — detta förenklade ViewModel-testning med 40% tack vare förutsägbara tillstånd.

stateIn() och SharingStarted: tre strategier

stateIn() — operator som omvandlar en kall Flow till en varm StateFlow. Den kräver angivelse av CoroutineScope (där den interna korutinen startas) och SharingStarted-strategi. Rätt val av SharingStarted påverkar kritiskt prestanda och livscykel för StateFlow.

kotlin
// Tre strategier för SharingStarted:

// 1. SharingStarted.Eagerly — startar omedelbart, stoppas inte
val eagerFlow = coldFlow.stateIn(
    scope = viewModelScope,
    started = SharingStarted.Eagerly,
    initialValue = 0
)

// 2. SharingStarted.Lazily — startar vid första prenumeranten, stoppas inte
val lazyFlow = coldFlow.stateIn(
    scope = viewModelScope,
    started = SharingStarted.Lazily,
    initialValue = 0
)

// 3. SharingStarted.WhileSubscribed() — startar när det finns prenumeranter,
//    stoppas efter stopTimeoutMillis (standard 0) efter att den senaste lämnat
val whileSubscribedFlow = coldFlow.stateIn(
    scope = viewModelScope,
    started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5000),
    initialValue = 0
)

WhileSubscribed(5000) — optimal strategi för ViewModel: efter att den senaste prenumeranten lämnat fortsätter den interna korutinen att arbeta i ytterligare 5 sekunder. Om användaren återvänder till skärmen inom denna tid återställs prenumerationen utan att strömmen startas om. Timeouten förhindrar frekventa omstarter vid snabb växling mellan skärmar. Enligt Googles tester (Android Performance, 2024) minskar WhileSubscribed med 5 sekunders timeout CPU-förbrukningen med 25% jämfört med Eagerly.

Kodexempel: StateFlow i Kotlin

Exempel 1: ViewModel med StateFlow och Compose

Fullständig sökskärm med sökfråga, resultat och laddningstillstånd. ViewModel använder sealed class UIState och StateFlow för reaktiv kommunikation med 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
    }
}

// I Compose:
@Composable
fun SearchScreen(viewModel: SearchViewModel = hiltViewModel()) {
    val uiState by viewModel.uiState.collectAsState()
    // ... UI som reagerar på tillstånden Loading, Results, Error
}

Exempel 2: StateFlow med Room och combine

Room (från och med version 2.4.0) stöder retur av Flow från DAO. Att kombinera flera Flow via combine är ett kraftfullt mönster för komplexa skärmar.

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 spårar automatiskt ändringar i tabellen orders och begär om data vid eventuella ändringar. StateFlow + Room — modern ersättning för kombinationen Room + LiveData. Enligt Google (Android Architecture Guide, 2025) rekommenderas kombinationen Flow + StateFlow + Room för alla Kotlin-projekt som kräver reaktiv UI-uppdatering vid databasändringar.

Vanliga frågor

Vad är konflation i StateFlow?

Konflation — mekanism där StateFlow endast behåller det senast skickade värdet. Om ett nytt värde skickas innan prenumeranten har behandlat det föregående, går mellanvärdet förlorat. Detta är viktigt för UI: om tillståndet ändras från Loading → Success → Error och UI inte hann rendera Success, går det direkt till Error utan onödig rendering. Konflation är en viktig Android-optimering som förhindrar överflödiga recompositioner i Compose.

Hur konverterar man LiveData till StateFlow?

Använd extension-funktionen liveData.asFlow() från biblioteket lifecycle-livedata-ktx, sedan .stateIn() för att konvertera till StateFlow. Omvänd konvertering — stateFlow.asLiveData(). Konvertering är användbar vid migrering från LiveData till StateFlow: du kan gradvis flytta ViewModel till StateFlow och låta den gamla View prenumerera via LiveData.

Varför kräver StateFlow ett initialt värde?

StateFlow måste alltid ha ett värde — detta är gränssnittets kontrakt: varje nyansluten prenumerant får omedelbart det aktuella tillståndet utan väntan.initiala värdet skickas till konstruktorn MutableStateFlow(initialValue) eller till operatorn stateIn(initialValue). Om tillståndet kan saknas, använd MutableStateFlow<T?>(null) med nullable-typ och hantera null i UI.

Är StateFlow trådsäker?

Ja, StateFlow är trådsäker: skrivning och läsning av value använder atomära operationer (CAS). Dock är collect() en suspend-funktion och måste startas i en korutin. Om emission och collect utförs på olika trådar garanterar StateFlow happens-before för alla value-operationer. För att samla StateFlow i View, använd lifecycleScope.launch { repeatOnLifecycle(STATE.STARTED) { stateFlow.collect { ... } } }.

Hur många StateFlow kan lagras i en ViewModel?

Det finns inga begränsningar, men det rekommenderas inte mer än 3-5 separata StateFlow per skärm. Om fler olika tillstånd behövs, kombinera dem till ett via sealed class eller data class. Varje StateFlow kräver allokering av ett Continuation-objekt vid insamling — hundra StateFlow kan skapa en märkbar belastning på GC. Enligt Googles rekommendation är en sealed class UIState per skärm den optimala balansen mellan läsbarhet och prestanda.

Sammanfattning

  • StateFlow — varm reaktiv behållare från Kotlin Coroutines (replay=1), som alltid behåller det senaste värdet.
  • StateFlow vs LiveData: StateFlow är inte beroende av Lifecycle, stöder korutiner och multiplattform; LiveData — automatisk prenumeration.
  • MutableStateFlow med private set och publicering av skrivskyddad StateFlow — standardmönster för ViewModel.
  • Sealed class som UIState — Googles rekommenderade UDF-metod för hantering av komplexa skärmtillstånd.
  • stateIn() med WhileSubscribed(5000) — optimal strategi för att omvandla kall Flow till StateFlow för ViewModel.
  • Room returnerar Flow från DAO — StateFlow + combine + Room ersätter kombinationen Room + LiveData.
  • Google rekommenderar StateFlow för nya Kotlin-projekt, särskilt i kombination med Jetpack Compose.

Vi utvecklar en mobil applikation nyckelfärdigt

IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.

Diskutera projektet

Läs också