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 View 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
คือ "ผู้บริโภค" StateFlow
เป็น hot ซึ่งแตกต่างจากโฟลว์ cold ที่สร้างขึ้นโดยใช้เครื่องมือสร้าง flow
กล่าวคือ การเก็บรวบรวมจากโฟลว์จะไม่ทริกเกอร์โค้ดของผู้ผลิต StateFlow
จะทำงานอยู่เสมอและอยู่ในหน่วยความจำ และจะมีโอกาสได้รับการเก็บขยะก็ต่อเมื่อไม่มีการอ้างอิงอื่นใดจากรูทการเก็บขยะ
เมื่อผู้บริโภครายใหม่เริ่มรวบรวมข้อมูลจากโฟลว์ ผู้บริโภครายดังกล่าวจะได้รับสถานะล่าสุดในสตรีมและสถานะต่อๆ มา คุณจะเห็นลักษณะการทำงานนี้ในคลาสอื่นๆ ที่สังเกตได้ เช่น LiveData
View
จะคอยฟัง StateFlow
เช่นเดียวกับโฟลว์อื่นๆ ดังนี้
class LatestNewsActivity : AppCompatActivity() {
private val latestNewsViewModel = // 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<Event<String>> = _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 ของ Flow
- การย้ายข้อมูลจาก LiveData ไปยัง Kotlin Flow
- แหล่งข้อมูลเพิ่มเติมเกี่ยวกับโครูทีนและโฟลว์ของ Kotlin