StateFlow และ SharedFlow

StateFlow และ SharedFlow เป็น Flow API ที่ช่วยให้โฟลว์ส่งการอัปเดตสถานะและส่งค่าไปยังผู้ใช้หลาย รายได้อย่างเหมาะสม

StateFlow

StateFlow เป็นโฟลว์ที่สังเกตได้ซึ่งเก็บสถานะและส่งการอัปเดตสถานะปัจจุบันและสถานะใหม่ไปยังตัวรวบรวม นอกจากนี้ คุณยังอ่านค่าสถานะปัจจุบันได้ผ่าน value พร็อพเพอร์ตี้ หากต้องการอัปเดตสถานะและส่งไปยังโฟลว์ ให้กำหนดค่าใหม่ให้กับ พร็อพเพอร์ตี้ valueของ MutableStateFlow คลาส

ใน Android StateFlow เหมาะอย่างยิ่งสำหรับคลาสที่ต้องเก็บสถานะที่เปลี่ยนแปลงได้ซึ่งสังเกตได้

เมื่อทำตามตัวอย่างจาก โฟลว์ Kotlin คุณจะแสดง StateFlow จาก LatestNewsViewModel เพื่อให้ View สามารถ รับฟังการอัปเดตสถานะ UI และทำให้สถานะหน้าจออยู่รอดจากการ เปลี่ยนแปลงการกำหนดค่าโดยธรรมชาติได้

class LatestNewsViewModel(
    private val newsRepository: NewsRepository
) : ViewModel() {

    // Backing property to avoid state updates from other classes
    private val _uiState = MutableStateFlow(LatestNewsUiState.Success(emptyList()))
    // The UI collects from this StateFlow to get its state updates
    val uiState: StateFlow<LatestNewsUiState> = _uiState

    init {
        viewModelScope.launch {
            newsRepository.favoriteLatestNews
                // Update UI with the latest favorite news
                // Writes to the value property of MutableStateFlow,
                // adding a new element to the flow and updating all
                // of its collectors
                .collect { favoriteNews ->
                    _uiState.value = LatestNewsUiState.Success(favoriteNews)
                }
        }
    }
}

// Represents different states for the LatestNews screen
sealed class LatestNewsUiState {
    data class Success(val news: List<ArticleHeadline>) : LatestNewsUiState()
    data class Error(val exception: Throwable) : LatestNewsUiState()
}

คลาสที่รับผิดชอบในการอัปเดต MutableStateFlow คือโปรดิวเซอร์ และคลาสทั้งหมดที่รวบรวมจาก StateFlow คือผู้ใช้ Unlike โฟลว์ เย็น ที่สร้างขึ้นโดยใช้ตัวสร้าง flow นั้น StateFlow เป็นโฟลว์ ร้อน: การรวบรวมจากโฟลว์จะไม่ทริกเกอร์โค้ดโปรดิวเซอร์ StateFlow จะทำงานและอยู่ในหน่วยความจำเสมอ และจะมีสิทธิ์ได้รับการเก็บกวาดหน่วยความจำก็ต่อเมื่อไม่มีการอ้างอิงอื่นๆ จากรูทการเก็บกวาดหน่วยความจำ

เมื่อผู้ใช้ใหม่เริ่มรวบรวมจากโฟลว์ ผู้ใช้จะได้รับสถานะล่าสุดในสตรีมและสถานะต่อๆ มา คุณจะเห็นลักษณะการทำงานนี้ ในคลาสอื่นๆ ที่สังเกตได้ เช่น LiveData

View จะรับฟัง StateFlow เช่นเดียวกับโฟลว์อื่นๆ ดังนี้

class LatestNewsActivity : ComponentActivity() {
    private val latestNewsViewModel: LatestNewsViewModel = TODO() // getViewModel()

    override fun onCreate(savedInstanceState: Bundle?) {
        // ...
        // Start a coroutine in the lifecycle scope
        lifecycleScope.launch {
            // repeatOnLifecycle launches the block in a new coroutine every time the
            // lifecycle is in the STARTED state (or above) and cancels it when it's STOPPED.
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                // Trigger the flow and start listening for values.
                // Note that this happens when lifecycle is STARTED and stops
                // collecting when the lifecycle is STOPPED
                latestNewsViewModel.uiState.collect { uiState ->
                    // New value received
                    when (uiState) {
                        is LatestNewsUiState.Success -> showFavoriteNews(uiState.news)
                        is LatestNewsUiState.Error -> showError(uiState.exception)
                    }
                }
            }
        }
    }
}

หากต้องการแปลงโฟลว์เป็น StateFlow ให้ใช้ stateIn โอเปอเรเตอร์ระดับกลาง

StateFlow, Flow และ LiveData

StateFlow และ LiveData มี ความคล้ายคลึงกัน ทั้ง 2 อย่างเป็นคลาสที่เก็บข้อมูลที่สังเกตได้ และทั้ง 2 อย่างใช้รูปแบบที่คล้ายกันเมื่อใช้ในสถาปัตยกรรมแอป

อย่างไรก็ตาม โปรดทราบว่า StateFlow และ LiveData มีลักษณะการทำงานที่แตกต่างกันดังนี้

  • StateFlow กำหนดให้ส่งสถานะเริ่มต้นไปยังคอนสตรักเตอร์ แต่ LiveData ไม่กำหนด
  • LiveData.observe() จะยกเลิกการลงทะเบียนผู้ใช้โดยอัตโนมัติเมื่อมุมมองเปลี่ยนเป็นสถานะ STOPPED ในขณะที่การรวบรวมจาก StateFlow หรือโฟลว์อื่นๆ จะไม่หยุดการรวบรวมโดยอัตโนมัติ หากต้องการให้มีลักษณะการทำงานเหมือนกัน คุณต้องรวบรวมโฟลว์จากบล็อก Lifecycle.repeatOnLifecycle

ทำให้โฟลว์เย็นกลายเป็นโฟลว์ร้อนโดยใช้ shareIn

StateFlow เป็นโฟลว์ ร้อน ซึ่งจะอยู่ในหน่วยความจำตราบใดที่มีการรวบรวมโฟลว์หรือมีการอ้างอิงอื่นๆ จากรูทการเก็บกวาดหน่วยความจำ คุณสามารถเปลี่ยนโฟลว์เย็นให้เป็นโฟลว์ร้อนได้โดยใช้ shareIn โอเปอเรเตอร์

เมื่อใช้ callbackFlow ที่สร้างขึ้นใน โฟลว์ Kotlin เป็น ตัวอย่าง แทนที่จะให้ตัวรวบรวมแต่ละรายการสร้างโฟลว์ใหม่ คุณสามารถแชร์ ข้อมูลที่ดึงมาจาก Firestore ระหว่างตัวรวบรวมได้โดยใช้ shareIn โดยคุณต้องส่งข้อมูลต่อไปนี้

  • CoroutineScope ที่ใช้แชร์โฟลว์ ขอบเขตนี้ควรมีอายุการใช้งานนานกว่าผู้ใช้รายใดๆ เพื่อให้โฟลว์ที่แชร์ทำงานอยู่ได้นานเท่าที่จำเป็น
  • จำนวนรายการที่จะเล่นซ้ำให้กับตัวรวบรวมใหม่แต่ละรายการ
  • นโยบายลักษณะการทำงานเริ่มต้น

class NewsRemoteDataSource(
    private val externalScope: CoroutineScope
) {
    val latestNews: Flow<List<ArticleHeadline>> = flow {
        // ...
    }.shareIn(
        externalScope,
        replay = 1,
        started = SharingStarted.WhileSubscribed()
    )
}

ในตัวอย่างนี้ โฟลว์ latestNews จะเล่นซ้ำรายการล่าสุดที่ส่งไปยังตัวรวบรวมใหม่และจะยังคงทำงานอยู่ตราบใดที่ externalScope ยังทำงานอยู่และมีตัวรวบรวมที่ทำงานอยู่ นโยบายเริ่มต้น SharingStarted.WhileSubscribed() จะทำให้โปรดิวเซอร์ต้นทางทำงานอยู่ตราบใดที่มีผู้สมัครใช้บริการที่ทำงานอยู่ นอกจากนี้ ยังมีนโยบายเริ่มต้นอื่นๆ เช่น SharingStarted.Eagerly เพื่อเริ่มโปรดิวเซอร์ทันที หรือ SharingStarted.Lazily เพื่อเริ่มแชร์หลังจากที่ผู้สมัครใช้บริการรายแรกปรากฏขึ้นและทำให้โฟลว์ทำงานอยู่ตลอดไป

SharedFlow

ฟังก์ชัน shareIn จะแสดงผล SharedFlow ซึ่งเป็นโฟลว์ร้อนที่ส่งค่าไปยังผู้ใช้ทั้งหมดที่รวบรวมจากโฟลว์ SharedFlow เป็นการทั่วไปของ StateFlow ที่กำหนดค่าได้สูง

คุณสามารถสร้าง SharedFlow ได้โดยไม่ต้องใช้ shareIn ตัวอย่างเช่น คุณสามารถใช้ SharedFlow เพื่อส่งสัญญาณไปยังส่วนอื่นๆ ของแอปเพื่อให้เนื้อหาทั้งหมดรีเฟรชเป็นระยะๆ ในเวลาเดียวกัน นอกเหนือจากการดึงข้อมูลข่าวสารล่าสุดแล้ว คุณอาจต้องการรีเฟรชส่วนข้อมูลผู้ใช้ด้วยคอลเล็กชันหัวข้อที่ชื่นชอบ ในข้อมูลโค้ดต่อไปนี้ TickHandler จะแสดง SharedFlow เพื่อให้คลาสอื่นๆ ทราบเวลาที่จะรีเฟรชเนื้อหา เช่นเดียวกับ StateFlow ให้ใช้พร็อพเพอร์ตี้สำรองประเภท MutableSharedFlow ในคลาสเพื่อส่งรายการไปยังโฟลว์

// Class that centralizes when the content of the app needs to be refreshed
class TickHandler(
    private val externalScope: CoroutineScope,
    private val tickIntervalMs: Long = 5000
) {
    // Backing property to avoid flow emissions from other classes
    private val _tickFlow = MutableSharedFlow<Unit>(replay = 0)
    val tickFlow: SharedFlow<Unit> = _tickFlow

    init {
        externalScope.launch {
            while (true) {
                _tickFlow.emit(Unit)
                delay(tickIntervalMs)
            }
        }
    }
}

class NewsRepository(
    // ...
    private val tickHandler: TickHandler,
    private val externalScope: CoroutineScope
) {
    init {
        externalScope.launch {
            // Listen for tick updates
            tickHandler.tickFlow.collect {
                refreshLatestNews()
            }
        }
    }

    suspend fun refreshLatestNews() { /* ... */ }
    // ...
}

คุณสามารถปรับแต่งลักษณะการทำงานของ SharedFlow ได้ดังนี้

  • replay ช่วยให้คุณส่งค่าที่ส่งก่อนหน้านี้จำนวนหนึ่งรายการไปยังผู้สมัครใช้บริการใหม่ได้อีกครั้ง
  • onBufferOverflow ช่วยให้คุณระบุนโยบายสำหรับกรณีที่บัฟเฟอร์เต็มไปด้วยรายการที่จะส่ง ค่าเริ่มต้นคือ BufferOverflow.SUSPEND ซึ่งจะทำให้ผู้เรียกหยุดชั่วคราว ตัวเลือกอื่นๆ ได้แก่ DROP_LATEST หรือ DROP_OLDEST

MutableSharedFlow ยังมีพร็อพเพอร์ตี้ subscriptionCount ที่มีจำนวนตัวรวบรวมที่ทำงานอยู่ เพื่อให้คุณเพิ่มประสิทธิภาพตรรกะทางธุรกิจได้ตามความเหมาะสม MutableSharedFlow ยังมีฟังก์ชัน resetReplayCache หากคุณไม่ต้องการเล่นซ้ำข้อมูลล่าสุดที่ส่งไปยังโฟลว์

แหล่งข้อมูลโฟลว์เพิ่มเติม