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 หากคุณไม่ต้องการเล่นซ้ำข้อมูลล่าสุดที่ส่งไปยังโฟลว์
แหล่งข้อมูลโฟลว์เพิ่มเติม
- โฟลว์ Kotlin ใน Android
- การทดสอบโฟลว์ Kotlin ใน Android
- สิ่งที่ควรทราบเกี่ยวกับโอเปอเรเตอร์ shareIn และ stateIn ของโฟลว์
- การย้ายข้อมูลจาก LiveData ไปยังโฟลว์ Kotlin
- แหล่งข้อมูลเพิ่มเติมสำหรับโครูทีนและโฟลว์ Kotlin