網站首頁 編程語言 正文
在 Jetpack 架構規范中, ViewModel 與 View 之間應該遵循單向數據流的通信方式,Events
永遠從 View 流向 VM ,而 State
從 VM 流向 View。
如果 ViewModel 對 View 暴露了不適當的接口類型,則會破壞單向數據流的形成。不適當的接口類型常見于以下兩點:
- 暴露 Mutable 狀態
- 暴露 Suspend 方法
暴露 Mutable 狀態
ViewModel 對外暴露的數據狀態,無論是 LiveData 或是 StateFlow 都應該使用 Immutable 的接口類型進行暴露而非 Mutable 的具體實現。View 只能單向訂閱這些狀態的變化,避免對狀態反向更新。
class MyViewModel: ViewModel() { private val _loading = MutableLiveData<Boolean>() val loading: LiveData<Boolean> get() = _loading }
未來避免暴露 Mutable 類型,我們需要像上面這樣處理,將 loading
的具體實現定義為一個 private
的 Mutable 類型,便于內部更新。
private val _loading : MutableStateFlow<Boolean?> = MutableStateFlow(null) val loading = _loading.asStateFlow()
StateFlow 的寫法也類似,但是通過 asStateFlow
可以少寫一個類型聲明,但是要注意此時不要使用 custom get(), 不然 asStateFlow
會執行多次。
每次都要多聲明一個帶劃線的私有變量會讓代碼顯得有些累贅,也正因如此,有 issue 希望 Kotlin 增加類似下面的語法使得對外對內可以暴露不同類型。
//https://youtrack.jetbrains.com/issue/KT-14663 private val loading = MutableLiveData<Boolean>() public get(): LiveData<Boolean>
在新語法還未出現的當下,一個讓代碼變整潔的思路是為 ViewModel 提取對外暴露的抽象類:
abstract class MyViewModel: ViewModel() { abstract val loading: LiveData<Boolean> } class MyViewModelImpl: MyViewModel() { override val loading = MutableLiveData<Boolean>() fun doSomeWork() { // ... loading.value = true } }
如上, MyViewModelImpl
內重寫的 loading
可以作為 Mutable 類型使用。雖然這種做法會增加了一個抽象類代碼量不減反增,但是它使 MyViewModelImpl
內的代碼更加簡潔,而且對外可以隱藏更多 ViewModel 的實現細節,封裝性更好。
但是需要特別注意的是,為了創建 MyViewModel
必須使用自定義 Factory:
val vm : MyViewModel by viewModels { MyViewModelFactory() }
如果你的工程引入了 Hilt ,那么可以通過 @Bind
綁定 ViewModel 的接口與實現,無需自定義 Factory 了,寫法跟以前一樣,直接使用 by viewModels()
即可
@Module @InstallIn(ViewModelComponent::class) abstract class MyViewModule { @Binds abstract fun MyViewModel(instance: MyViewModelImpl): MyViewModel } @HiltViewModel class MyViewModelImpl @Inject constructor() : MyViewModel()
暴露 Suspend 方法
相對于暴露 Mutable 狀態,暴露 Suspend 方法的錯誤則更為常見。
按照單向數據流的思想 ViewModel 需要提供 API 給 View 用于發送 Events,我們在定義 API 時需要注意避免使用 Suspend 函數,理由如下:
- 來自 ViewModel 的數據應該通過訂閱 UiState 獲取,因此 ViewModel 的其他方法方法不應該有返回值,而 suspend 函數會鼓勵返回值的出現。
- 理想的 MVVM 中 View 的職責僅僅是渲染 UI,業務邏輯盡量移動到 ViewModel 執行,利于單元測試的同時,
ViewModelScope
可以保證一些耗時任務的穩定執行。如果暴露掛起函數給 View,則協程需要在lifecycleScope
中啟動,在橫豎屏等場景中會中斷任務的進行。
因此,ViewModel 為 View 暴露的 API 應該是非掛起且無法返回值的方法,以下是官網的代碼實例:
// DO create coroutines in the ViewModel class LatestNewsViewModel( private val getLatestNewsWithAuthors: GetLatestNewsWithAuthorsUseCase ) : ViewModel() { private val _uiState = MutableStateFlow<LatestNewsUiState>(LatestNewsUiState.Loading) val uiState: StateFlow<LatestNewsUiState> = _uiState fun loadNews() { viewModelScope.launch { val latestNewsWithAuthors = getLatestNewsWithAuthors() _uiState.value = LatestNewsUiState.Success(latestNewsWithAuthors) } } } // Prefer observable state rather than suspend functions from the ViewModel class LatestNewsViewModel( private val getLatestNewsWithAuthors: GetLatestNewsWithAuthorsUseCase ) : ViewModel() { // DO NOT do this. News would probably need to be refreshed as well. // Instead of exposing a single value with a suspend function, news should // be exposed using a stream of data as in the code snippet above. suspend fun loadNews() = getLatestNewsWithAuthors() }
代碼中建議暴露一個普通的無返回值的 loadNews
,而 latestNewsWithAuthors
的信息應該通過訂閱 LatestNewsUiState
獲得 。
有一點讓人迷惑的是,官方文檔上有這么一句話:
Suspend functions in the ViewModel can be useful if instead of exposing state using a stream of data, only a single value needs to be emitted.
鏈接
對于單發數據的請求允許使用掛起函數返回。但我建議大家忘掉這句話,理由有兩點:
- 掛起函數的口子一開就容易不分場景的濫用,如果整體數據流結構造成破壞反而因小失大,索性應該從源頭禁止
- 理論上來說,UI 上不存在單發數據請求的必要性,完全可以通過良好的設計轉化成 UiState ,這也更符合響應式的編程模型。
原文鏈接:https://blog.csdn.net/vitaviva/article/details/123516701
相關推薦
- 2022-05-12 Kotlin 集合也可以進行+= -= 還可以根據條件進行刪除(removeIf)
- 2022-11-14 Asp.net?Core項目配置HTTPS支持_實用技巧
- 2022-02-14 jquery-選擇器、篩選器、樣式操作、文本操作、屬性操作、文檔處理
- 2022-04-15 C語言的位段與枚舉詳解_C 語言
- 2022-07-17 C++深入講解namespace與string關鍵字的使用_C 語言
- 2022-10-24 C語言詳解分析進程控制中進程終止的實現_C 語言
- 2022-09-20 Redis?SCAN命令詳解_Redis
- 2022-09-05 C語言中如何實現單鏈表刪除指定結點_C 語言
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支