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() — ตัวดำเนินการ terminal ของ 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 และไม่มี dependencies กับ Android

ตามแนวคิดแล้ว StateFlow เป็นคุณสมบัติรีแอคทีฟ: คุณอ่านค่าปัจจุบันผ่าน .value และสมัครสมาชิกการเปลี่ยนแปลงผ่าน .collect() โมเดลนี้เรียกว่า " hot flow" — แหล่งข้อมูลทำงานอยู่โดยไม่ขึ้นกับการมีสมาชิก ตรงกันข้ามกับ " cold" flows ที่สร้างผ่าน 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?>
ConflationConflated — ข้ามค่ากลางผ่าน 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 พร้อม property 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) ค่าไม่เป็น 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() — ตัวดำเนินการที่แปลง cold Flow เป็น hot 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 คืออะไร?

Conflation — กลไกที่ StateFlow เก็บเฉพาะค่าที่ส่งล่าสุดเท่านั้น หากมีการส่งค่าใหม่ก่อนที่สมาชิกจะประมวลผลค่าก่อนหน้า ค่ากลางจะหายไป ซึ่งสำคัญสำหรับ UI: หากสถานะเปลี่ยนจาก Loading → Success → Error และ UI ไม่มีเวลาเรนเดอร์ Success มันจะเปลี่ยนเป็น Error ทันทีโดยไม่มีการเรนเดอร์ที่ไม่จำเป็น Conflation เป็นการเพิ่มประสิทธิภาพหลักของ Android ที่ป้องกันการ recomposition ที่มากเกินไปใน Compose

จะแปลง LiveData เป็น StateFlow ได้อย่างไร?

ใช้ฟังก์ชัน extension 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 และต้องเริ่มในคอรูนีน หาก emission และ 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 — คอนเทนเนอร์รีแอคทีฟแบบ hot จาก Kotlin Coroutines (replay=1) ที่เก็บค่าล่าสุดเสมอ
  • StateFlow vs LiveData: StateFlow ไม่ขึ้นกับ Lifecycle รองรับคอรูนีนและหลายแพลตฟอร์ม; LiveData — การสมัครสมาชิกอัตโนมัติ
  • MutableStateFlow พร้อม private set และการเผยแพร่ StateFlow แบบอ่านอย่างเดียว — แพทเทิร์นมาตรฐานสำหรับ ViewModel
  • Sealed class เป็น UIState — แนวทาง UDF ที่ Google แนะนำสำหรับการจัดการสถานะหน้าจอที่ซับซ้อน
  • stateIn() กับ WhileSubscribed(5000) — กลยุทธ์ที่เหมาะสมที่สุดสำหรับการแปลง cold Flow เป็น StateFlow สำหรับ ViewModel
  • Room คืน Flow จาก DAO — StateFlow + combine + Room แทนที่คู่ Room + LiveData
  • Google แนะนำ StateFlow สำหรับโปรเจกต์ใหม่บน Kotlin โดยเฉพาะเมื่อใช้กับ Jetpack Compose

เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร

IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ

อ่านเพิ่มเติม