MVI
MVI 架構(gòu)核心思想是單一可信數(shù)據(jù)源。
ViewModel
中需要維護(hù)著一個(gè) UiState
( 一般來說會(huì)是個(gè) data class
),這個(gè) UiState
包含了 UI 層所需要的所有數(shù)據(jù)泽西,或者說描述了 UI 層的所有狀態(tài)卦绣,同時(shí)這個(gè) UiState
應(yīng)該具備通知觀察者更新的能力,例如使用 StateFlow
袋坑。
UI 層應(yīng)該僅通過該 UiState
渲染咙崎。那么對于這個(gè)頁面的 Composable
函數(shù)來說优幸,入?yún)⒅斜硎緮?shù)據(jù)的部分應(yīng)該只有一個(gè) UiState
。
fun NavGraphBuilder.registerLoginPage(navController: NavController){
composable("login"){
val viewModel: LoginViewModel = viewModel()
val uiState = viewModel.uiStateFlow.collectAsState().value
LoginPage(uiState = uiState)
}
}
@Composable
private fun LoginPage(
uiState: LoginUiState,
) {
// do somethings
}
事件上浮
事件上浮是指應(yīng)該將事件處理盡可能上浮褪猛,一般來說應(yīng)該上浮到頁面的 Composable
函數(shù)的上一級网杆。
也就是說,頁面的 Composable
函數(shù)應(yīng)該包含了這個(gè)頁面所有的事件回調(diào)伊滋。
上一級是指頁面路由注冊的地方碳却,我們可以將其視為 Activity/Fragment
,在這里拿到所有的事件回調(diào)笑旺,并交給 ViewModel
昼浦。
fun NavGraphBuilder.registerLoginPage(navController: NavController) {
composable("login") {
val viewModel: LoginViewModel = viewModel()
val uiState = viewModel.uiStateFlow.collectAsState().value
LoginPage(
uiState = uiState,
onBackClick = navController::popBackStack,
onLoginClick = viewModel::onLoginClick,
)
}
}
@Composable
private fun LoginPage(
uiState: LoginUiState,
onBackClick: () -> Unit,
onLoginClick: () -> Unit,
) {
// do somethings
}
ViewModel 中的事件
一般來說事件是在 UI 層通過監(jiān)聽用戶手勢而被觸發(fā)的,但也有一些事件是在 ViewModel
層被觸發(fā)的燥撞。
例如網(wǎng)絡(luò)請求失敗后的錯(cuò)誤消息提示座柱,結(jié)束頁面或者打開新頁面等等迷帜。
我們可以先考慮網(wǎng)絡(luò)請求成功后打開新頁面這個(gè)場景物舒。
鑒于 Compose 提供了副作用相關(guān)的一些函數(shù),以及 MVI 單一可信數(shù)據(jù)源的思想戏锹,我們可能會(huì)考慮在 UiState
中提供一個(gè) Boolean
值表示是否需要打開頁面冠胯,并將其作為 Key 通過副作用函數(shù)打開新頁面。
真的這么做的話可能會(huì)發(fā)現(xiàn)一些問題锦针,例如打開新的頁面后退出回到當(dāng)前頁面荠察,結(jié)果又自動(dòng)打開了新頁面置蜀,此時(shí)我們會(huì)意識到應(yīng)該在頁面打開后更新字段為 false
,然后可能還會(huì)發(fā)現(xiàn)其他問題悉盆。
這么做無疑是很麻煩的盯荤,本質(zhì)上是因?yàn)槲覀?strong>混淆了事件與數(shù)據(jù)這兩者的概念。
數(shù)據(jù)是用于填充 UI 元素的對象焕盟,這些元素會(huì)因?yàn)椴煌臄?shù)據(jù)而有所區(qū)別秋秤,并在視覺上有所體現(xiàn)。
事件是指軟件在運(yùn)行過程中的某個(gè)時(shí)間點(diǎn)由于某些特定的原因 ( 例如用戶手勢 ) 而需要做出的一系列變更中的一個(gè)脚翘。
那么顯而易見的是灼卢,打開頁面就應(yīng)該屬于事件。
具體而言来农,我們應(yīng)該如何處理呢鞋真。
我們可以在 ViewModel
中定義一個(gè)叫 openMainPageFlow
的對象,它應(yīng)該是個(gè) SharedFlow
沃于,然后 UI 層通過監(jiān)聽這個(gè) Flow 來打開頁面涩咖。
// LoginViewModel.kt
class LoginViewModel : ViewModel() {
private val _openMainPageFlow: MutableSharedFlow<Unit> = MutableSharedFlow()
val openMainPageFlow: SharedFlow<Unit> = _openMainPageFlow
// other code ...
}
// LoginNavigation.kt
fun NavGraphBuilder.registerLoginPage(navController: NavController) {
composable("login") {
val viewModel: LoginViewModel = viewModel()
val uiState = viewModel.uiStateFlow.collectAsState().value
LoginPage(
uiState = uiState,
onBackClick = navController::popBackStack,
onLoginClick = viewModel::onLoginClick,
)
val openMainPageFlow = viewModel.openMainPageFlow
LaunchedEffect(openMainPageFlow) {
openMainPageFlow.collect {
navController.navigate("main")
}
}
}
}