iT邦幫忙

2026 iThome 鐵人賽

DAY 6
0
Software Development

從 Fragment 到 Compose — 老 Android App 重寫的架構取捨系列 第 6

Day 06 | UiState / UiEvent / UiEffect:單向資料流但不背 MVI 的儀式

  • 分享至 

  • xImage
  •  
  1. UiState:畫面「現在長什麼樣」
  2. UiEvent:畫面「發生了什麼」
  3. UiEffect:只需要發生一次的事情

老專案常常把導航或其他一次性事件寫成 LiveData 旗標。
導航觸發後,清除導航旗標 LiveData 以避免前一頁留存錯誤的旗標。

fun clearNavigateLiveData() {
    _navigateToDetail.value = false
}

fun clearErrorMessage() {
    _errorMessage.value = null
}

你會看到專案中常這樣控制一次性事件:

ViewModel:
private val _navigateToDetail = MutableLiveData<Boolean>()
val navigateToDetail: LiveData<Boolean> = _navigateToDetail

按下按鈕:
_navigateToDetail.value = true

Fragment:
viewModel.navigateToDetail.observe(viewLifecycleOwner) { navigate ->
    if (navigate == true) {
        findNavController().navigate(...)
        viewModel.clearNavigateToDetail()
    }
}

這是一個消耗(消費)並重置的 pattern。

但他其實不是一個 UI 狀態。發出導航行為後就就消滅,不需要把這個狀態長期保存。

依現代框架:

先定義 UI Effect:
sealed interface DetailUiEffect {
    data object NavigateBack : DetailUiEffect
}

在 ViewModel 內使用 emit 把值送入 Flow:
class DetailViewModel : ViewModel() {

    private val _effect =
        MutableSharedFlow<DetailUiEffect>()

    val effect = _effect.asSharedFlow()

    fun save() {
        viewModelScope.launch {
            // save success

            _effect.emit(
                DetailUiEffect.NavigateBack
            )
        }
    }
}

UI:
lifecycleScope.launch {
    viewModel.effect.collect { effect ->
        when (effect) {
            DetailUiEffect.NavigateBack ->
                findNavController().popBackStack()
        }
    }
}

上一篇
Day 05|網路邊界:後端信封與 `d: Any?` 的約束,`AppResult` 的誕生 | core:network |
系列文
從 Fragment 到 Compose — 老 Android App 重寫的架構取捨6
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言