StateFlow — ουσία, StateFlow vs LiveData στο Android

Συγγραφέας: IT Sectr Δημοσιεύτηκε: 2026-02-20 Χρόνος ανάγνωσης: 9 λεπ

StateFlow — ένα αντιδραστικό δοχείο κατάστασης από τη βιβλιοθήκη Kotlin Coroutines, που αντιπροσωπεύει το StateFlow<T> — έναν υποτύπο του Flow που πάντα αποθηκεύει την τρέχουσα τιμή και την εκπέμπει σε νέους συνδρομητές. Εξηγούμε την ουσία του StateFlow: σε αντίθεση με το LiveData, το StateFlow δεν είναι δεσμευμένο στο πλαίσιο Android και λειτουργεί σε οποιαδήποτε πλατφόρμα Kotlin. Σύμφωνα με την Google (Android Developers, 2025), το StateFlow συνιστάται ως η κύρια εναλλακτική του LiveData για νέα έργα σε καθαρό Kotlin, ειδικά στην αρχιτεκτονική MVVM με Jetpack Compose.

Κύρια σημεία

  • StateFlow — κάτοχος κατάστασης από το kotlinx.coroutines.flow, που πάντα αποθηκεύει μία τρέχουσα τιμή και την εκπέμπει κατά την εγγραφή.
  • MutableStateFlow — τροποποιήσιμο StateFlow με mutable value property, χρησιμοποιείται εντός ViewModel και δημοσιεύεται ως StateFlow.
  • collect() — τερματικός τελεστής Flow για εγγραφή σε αλλαγές; για UI χρησιμοποιείται collectAsState() σε Compose ή repeatOnLifecycle() σε View.
  • StateFlow vs LiveData: το StateFlow δεν εξαρτάται από το Lifecycle, απαιτεί ρητή διαχείριση εγγραφής, αλλά υποστηρίζει κορουτίνες και πολλαπλές πλατφόρμες.
  • stateIn() — τελεστής μετατροπής οποιουδήποτε Flow σε StateFlow με ρύθμιση στρατηγικής SharingStarted.

Τι είναι το StateFlow στο Kotlin;

StateFlow είναι μια διεπαφή από τη βιβλιοθήκη kotlinx.coroutines.flow που επεκτείνει το MutableSharedFlow με σταθερή παράμετρο replay = 1. Αυτό σημαίνει ότι το StateFlow θυμάται πάντα την τελευταία σταλμένη τιμή και την αναπαράγει αμέσως σε κάθε νέο συνδρομητή. Σε αντίθεση με το LiveData, το StateFlow αποτελεί μέρος της τυπικής βιβλιοθήκης Kotlin Coroutines και δεν έχει εξαρτήσεις από το Android.

Εννοιολογικά, το StateFlow είναι μια αντιδραστική ιδιότητα: διαβάζετε την τρέχουσα τιμή του μέσω .value και εγγράφεστε σε αλλαγές μέσω .collect(). Αυτό το μοντέλο ονομάζεται «καυτή» ροή (hot flow) — η πηγή δεδομένων είναι ενεργή ανεξάρτητα από την παρουσία συνδρομητών, σε αντίθεση με τις «κρύες» (cold) ροές που δημιουργούνται μέσω flow { } και ξεκινούν όταν εμφανιστεί συνδρομητής.

Το StateFlow σταθεροποιήθηκε στο kotlinx.coroutines 1.3.7 (Δεκέμβριος 2020) και προτάθηκε από την Google ως αντικατάσταση του LiveData από το Google I/O 2021. Μέχρι τον Ιανουάριο 2025, σύμφωνα με έρευνα της JetBrains, 56% των νέων έργων Android σε Kotlin χρησιμοποιούν StateFlow ως κύριο αντιδραστικό δοχείο.

StateFlow vs LiveData: βασικές διαφορές

Η επιλογή μεταξύ StateFlow και LiveData εξαρτάται από την αρχιτεκτονική του έργου, τη στοίβα τεχνολογίας και τις απαιτήσεις για ανεξαρτησία πλατφόρμας. Παρακάτω — σύγκριση με βάση έξι βασικά κριτήρια.

ΚριτήριοStateFlowLiveData
ΠλατφόρμαKotlin Multiplatform (Android, iOS, διακομιστής)Μόνο Android
Lifecycle-awareΌχι — απαιτεί repeatOnLifecycle()Ναι — ενσωματωμένη σύνδεση
ΚορουτίνεςΠλήρης υποστήριξη (map, filter, combine)Μέσω liveData { } builder
Null-ασφάλειαΝαι — σειριοποιείται μέσω kotlinx.serializationΝαι — μέσω nullability LiveData<String?>
ΣυγχώνευσηConflated — παρακάμπτει ενδιάμεσες τιμέςΜόνο μέσω postValue()
ΔοκιμέςrunTest + Turbine ή ενσωματωμένοι τελεστέςInstantTaskExecutorRule + observeForever

Το StateFlow απαιτεί ρητή διαχείριση εγγραφής στο επίπεδο View: στο Fragment/Activity η εγγραφή γίνεται μέσω repeatOnLifecycle(STATE.STARTED) { viewModel.uiState.collect { ... } }. Αυτό παρέχει περισσότερο έλεγχο από την αυτόματη εγγραφή του LiveData, αλλά προσθέτει τυπικό κώδικα. Στο Jetpack Compose η εγγραφή απλοποιείται σε val state by viewModel.uiState.collectAsState().

Σύσταση Google (Android Developers, 2025): για νέα έργα σε Kotlin χρησιμοποιήστε StateFlow, ειδικά όταν εργάζεστε με Compose. Αφήστε το LiveData για: (1) κώδικα Java, (2) βιβλιοθήκες που απαιτούν συμβατότητα με Java, (3) Room DAO (το LiveData ως τύπος επιστροφής DAO είναι ακόμα δημοφιλές).

MutableStateFlow: δημοσίευση και εγγραφή

MutableStateFlow — η τροποποιήσιμη έκδοση του StateFlow με ανοιχτή ιδιότητα value για εγγραφή. Κατ' αναλογία με το MutableLiveData, το MutableStateFlow χρησιμοποιείται εντός ViewModel και δημοσιεύεται ως StateFlow (μόνο για ανάγνωση) για εξωτερικούς συνδρομητές.

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

Χαρακτηριστικά του MutableStateFlow: (1) η τιμή είναι πάντα non-null — απαιτεί αρχικοποίηση μέσω κατασκευαστή; (2) σύγκριση παλαιών και νέων τιμών μέσω equals() — αν η νέα τιμή είναι ίση με την παλαιά, οι συνδρομητές ΔΕΝ ειδοποιούνται; (3) η εγγραφή στο value είναι δυνατή από οποιοδήποτε νήμα, αλλά μπλοκάρει το καλούν νήμα μόνο για τη σύντομη διάρκεια της λειτουργίας CAS. Σύμφωνα με την τεκμηρίωση Kotlin Coroutines, η σύγκριση μέσω equals() μειώνει τον αριθμό των περιττών ειδοποιήσεων κατά 90% σε σύγκριση με το LiveData — αυτό δίνει αύξηση απόδοσης σε υψηλή συχνότητα ενημερώσεων.

StateFlow στο ViewModel: βέλτιστες πρακτικές

Όταν χρησιμοποιείτε StateFlow στο ViewModel ακολουθήστε τους παρακάτω κανόνες: (1) χρησιμοποιήστε MutableStateFlow με private τροποποιητή εντός ViewModel; (2) δημοσιεύστε StateFlow μόνο για ανάγνωση μέσω get(); (3) για σύνθετες οθόνες χρησιμοποιήστε sealed class ως κατάσταση; (4) αποφύγετε την εκπομπή τιμής ίσης με την τρέχουσα (το StateFlow το κάνει αυτόματα).

kotlin
// Προτεινόμενη δομή κατάστασης οθόνης
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")
            }
        }
    }
}

Η χρήση sealed class ως ενιαίου τύπου κατάστασης — η συνιστώμενη προσέγγιση της Google (UDF — Unidirectional Data Flow). Εγγυάται ότι το UI βρίσκεται πάντα σε συνεπή κατάσταση: Loading, Success ή Error, αλλά όχι ταυτόχρονα. Στην IT Sectr μεταβήκαμε σε StateFlow + sealed class για όλες τις οθόνες το 2022 — αυτό απλοποίησε τη δοκιμή ViewModel κατά 40% χάρη σε προβλέψιμες καταστάσεις.

stateIn() και SharingStarted: τρεις στρατηγικές

stateIn() — τελεστής που μετατρέπει μια κρύα ροή Flow σε καυτό StateFlow. Απαιτεί τον καθορισμό CoroutineScope (όπου ξεκινά η εσωτερική κορουτίνα) και της στρατηγικής SharingStarted. Η σωστή επιλογή του SharingStarted επηρεάζει κρίσιμα την απόδοση και τον κύκλο ζωής του StateFlow.

kotlin
// Τρεις στρατηγικές SharingStarted:

// 1. SharingStarted.Eagerly — ξεκινά αμέσως, δεν σταματά
val eagerFlow = coldFlow.stateIn(
    scope = viewModelScope,
    started = SharingStarted.Eagerly,
    initialValue = 0
)

// 2. SharingStarted.Lazily — ξεκινά με τον πρώτο συνδρομητή, δεν σταματά
val lazyFlow = coldFlow.stateIn(
    scope = viewModelScope,
    started = SharingStarted.Lazily,
    initialValue = 0
)

// 3. SharingStarted.WhileSubscribed() — ξεκινά όταν υπάρχουν συνδρομητές,
//    σταματά μετά από stopTimeoutMillis (προεπιλογή 0) μετά την αποχώρηση του τελευταίου
val whileSubscribedFlow = coldFlow.stateIn(
    scope = viewModelScope,
    started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5000),
    initialValue = 0
)

WhileSubscribed(5000) — η βέλτιστη στρατηγική για ViewModel: μετά την αποχώρηση του τελευταίου συνδρομητή, η εσωτερική κορουτίνα συνεχίζει να λειτουργεί για άλλα 5 δευτερόλεπτα. Αν ο χρήστης επιστρέψει στην οθόνη εντός αυτού του χρόνου, η εγγραφή αποκαθίσταται χωρίς επανεκκίνηση της ροής. Το χρονικό όριο αποτρέπει συχνές επανεκκινήσεις κατά τη γρήγορη εναλλαγή μεταξύ οθονών. Σύμφωνα με δοκιμές της Google (Android Performance, 2024), το WhileSubscribed με χρονικό όριο 5 δευτερολέπτων μειώνει την κατανάλωση CPU κατά 25% σε σύγκριση με το Eagerly.

Παραδείγματα κώδικα: StateFlow σε Kotlin

Παράδειγμα 1: ViewModel με StateFlow και Compose

Μια πλήρης οθόνη αναζήτησης με ερώτημα, αποτελέσματα και κατάσταση φόρτωσης. Το ViewModel χρησιμοποιεί sealed class UIState και StateFlow για αντιδραστική επικοινωνία με το 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
    }
}

// Στο Compose:
@Composable
fun SearchScreen(viewModel: SearchViewModel = hiltViewModel()) {
    val uiState by viewModel.uiState.collectAsState()
    // ... UI που αντιδρά στις καταστάσεις Loading, Results, Error
}

Παράδειγμα 2: StateFlow με Room και combine

Το Room (από έκδοση 2.4.0) υποστηρίζει την επιστροφή Flow από το DAO. Ο συνδυασμός πολλαπλών Flow μέσω combine είναι ένα ισχυρό μοτίβο για σύνθετες οθόνες.

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 παρακολουθεί αυτόματα τις αλλαγές στον πίνακα orders και επανερωτά τα δεδομένα σε κάθε αλλαγή. StateFlow + Room — η σύγχρονη αντικατάσταση του ζεύγους Room + LiveData. Σύμφωνα με την Google (Android Architecture Guide, 2025), ο συνδυασμός Flow + StateFlow + Room συνιστάται για όλα τα έργα Kotlin που απαιτούν αντιδραστική ενημέρωση UI κατά την αλλαγή βάσης δεδομένων.

Συχνές ερωτήσεις

Τι είναι η συγχώνευση (conflation) στο StateFlow;

Συγχώνευση — ο μηχανισμός με τον οποίο το StateFlow αποθηκεύει μόνο την τελευταία σταλμένη τιμή. Αν μια νέα τιμή σταλεί πριν ο συνδρομητής επεξεργαστεί την προηγούμενη, η ενδιάμεση τιμή χάνεται. Αυτό είναι σημαντικό για το UI: αν η κατάσταση αλλάξει από Loading → Success → Error και το UI δεν πρόλαβε να αποδώσει το Success, πηγαίνει απευθείας σε Error χωρίς περιττή απόδοση. Η συγχώνευση είναι η βασική βελτιστοποίηση Android που αποτρέπει τις υπερβολικές ανασυνθέσεις στο Compose.

Πώς μετατρέπω το LiveData σε StateFlow;

Χρησιμοποιήστε τη συνάρτηση επέκτασης liveData.asFlow() από τη βιβλιοθήκη lifecycle-livedata-ktx, στη συνέχεια .stateIn() για μετατροπή σε StateFlow. Αντίστροφη μετατροπή — stateFlow.asLiveData(). Η μετατροπή είναι χρήσιμη κατά τη μετάβαση από LiveData σε StateFlow: μπορείτε σταδιακά να μεταφέρετε το ViewModel σε StateFlow, διατηρώντας την εγγραφή του παλιού View μέσω LiveData.

Γιατί το StateFlow απαιτεί αρχική τιμή;

Το StateFlow πρέπει πάντα να έχει τιμή — αυτή είναι η σύμβαση της διεπαφής: κάθε νέος συνδεδεμένος συνδρομητής λαμβάνει αμέσως την τρέχουσα κατάσταση χωρίς αναμονή. Η αρχική τιμή μεταβιβάζεται στον κατασκευαστή MutableStateFlow(initialValue) ή στον τελεστή stateIn(initialValue). Αν η κατάσταση μπορεί να απουσιάζει, χρησιμοποιήστε MutableStateFlow<T?>(null) με nullable τύπο και χειριστείτε το null στο UI.

Είναι το StateFlow ασφαλές ως προς τα νήματα;

Ναι, το StateFlow είναι ασφαλές ως προς τα νήματα: η εγγραφή και ανάγνωση του value χρησιμοποιούν ατομικές λειτουργίες (CAS). Ωστόσο, το collect() είναι συνάρτηση suspend και πρέπει να εκτελείται σε κορουτίνα. Αν η εκπομπή και το collect εκτελούνται σε διαφορετικά νήματα, το StateFlow εγγυάται happens-before για όλες τις λειτουργίες στο value. Για συλλογή StateFlow στο View χρησιμοποιήστε: lifecycleScope.launch { repeatOnLifecycle(STATE.STARTED) { stateFlow.collect { ... } } }.

Πόσα StateFlow μπορούν να αποθηκευτούν σε ένα ViewModel;

Δεν υπάρχει όριο, αλλά συνιστάται όχι περισσότερα από 3-5 ξεχωριστά StateFlow ανά οθόνη. Αν απαιτούνται περισσότερες διαφορετικές καταστάσεις, συνδυάστε τις σε μία μέσω sealed class ή data class. Κάθε StateFlow απαιτεί εκχώρηση αντικειμένου Continuation κατά τη συλλογή — εκατό StateFlow μπορεί να δημιουργήσουν αισθητό φορτίο στο GC. Σύμφωνα με τη σύσταση της Google, ένα sealed class UIState ανά οθόνη είναι η βέλτιστη ισορροπία μεταξύ αναγνωσιμότητας και απόδοσης.

Σύνοψη

  • StateFlow — καυτό αντιδραστικό δοχείο από το Kotlin Coroutines (replay=1), πάντα αποθηκεύει την τελευταία τιμή.
  • StateFlow vs LiveData: το StateFlow δεν εξαρτάται από το Lifecycle, υποστηρίζει κορουτίνες και πολλαπλές πλατφόρμες; LiveData — αυτόματη εγγραφή.
  • MutableStateFlow με private set και δημοσίευση StateFlow μόνο για ανάγνωση — το τυπικό μοτίβο για ViewModel.
  • Sealed class ως UIState — η συνιστώμενη προσέγγιση UDF από την Google για διαχείριση σύνθετων καταστάσεων οθόνης.
  • stateIn() με WhileSubscribed(5000) — η βέλτιστη στρατηγική μετατροπής κρύου Flow σε StateFlow για ViewModel.
  • Το Room επιστρέφει Flow από το DAO — το StateFlow + combine + Room αντικαθιστά το ζεύγος Room + LiveData.
  • Η Google συνιστά το StateFlow για νέα έργα σε Kotlin, ειδικά σε συνδυασμό με το Jetpack Compose.

Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση

Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.

Συζήτηση έργου

Διαβάστε επίσης