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 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 ด้วย หากคุณไม่ต้องการเล่นข้อมูลล่าสุดที่ส่งไปยังโฟลว์ซ้ำ

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