StateFlow และ SharedFlow

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

StateFlow

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

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

จากตัวอย่างใน Kotlin Flow คุณจะแสดง 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 คือผู้บริโภค flowStateFlow การรวบรวมจากโฟลว์จะไม่ทริกเกอร์โค้ดของโปรดิวเซอร์ 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 Flows เป็นตัวอย่าง แทนที่จะให้ตัวรวบรวมแต่ละตัวสร้างโฟลว์ใหม่ คุณสามารถแชร์ข้อมูลที่ดึงมาจาก 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 ฟังก์ชันในกรณีที่คุณไม่ต้องการเล่นข้อมูลล่าสุดที่ส่งไปยังโฟลว์ซ้ำ

แหล่งข้อมูลเพิ่มเติมเกี่ยวกับโฟลว์