jetpack compose 開發架構選擇探討(一)
最近jetpack compose發布了正式版本,在jetpack compose剛出來的時候就一直有在關注這個全新的ui框架,但是一直沒有基于它去做一個完整的專案,只是去了解這個框架的原理、特性等,最近基于jetpack compose做了一個webrtc的視頻通話專案( webrtc_compose ),在專案開發程序中進一步加深了對jetpack compose的理解,尤其思考了這個全新的ui框架到底適合哪種開發架構,
開發架構選擇
雖然jetpack compose已經放出了1.0.0的正式版本,但是它離實際大規模應用可能還有一大段路要走,因為一個全新的框架需要時間來完善相關的周邊庫,以及需要大型專案來驗證選擇合適的開發架構,今天我就來簡單的講講目前主流的開發架構和jetpack compose的結合以及優缺點和目前需要解決的問題,
原有安卓開發架構的選擇
在安卓原有view體系中,比較流行的開發架構有MVC、MVP、MVVM、MVI、CLEAN等,由于jetpack compose是宣告式ui框架,對于需要持有view參考的mvc mvp等顯然無法適用,同時由于clean的重點在于資料以及邏輯的分層,在ui層可以選用MVVM和MVI等,所以本文也不會分析,因此我們主要來分析下MVVM和MVI和jetpack compose的結合
本文提到的例子具體代碼都在compose_architecture中,有需要的可以自取,同時也可以幫忙點點贊
MVVM
說到MVVM開發架構,其實對于原有的安卓view體系中的MVVM并不是完全的MVVM,因為MVVM最初就是為宣告式ui來設計的,而原有的安卓view體系并不是宣告式ui,因此使用起來總有些不倫不類,不過現在jetpack compose的出現完美的解決這個問題,由于jetpack compose和jetpack viewmodel完美兼容,因此在jetpack compose中實作MVVM和原有view體系中差別并不大,區別就是可以用宣告式的方式更方便的完成ui開發,

我們來看個簡單的例子,我們用MVVM實作一個簡單的add count
首先先實作下Content(jetpack compose提倡單向資料流,即將狀態提升到Screen中,Content不包含狀態,只是單純的ui界面,便于測驗,具體參考官方教程 (jetpack compose 狀態)
@Composable
fun Content1(count: Int, click: () -> Unit, click1: () -> Unit) {
Column(
Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(text = "The count is $count")
Button(onClick = click) {
Text(text = "goto screen2")
}
Button(onClick = click1) {
Text(text = "add")
}
}
}
然后我們分析下這個例子只有一個count 狀態和add 操作,因此這樣來實作viewModel,這里基于jetpack viewmodel和livedata組件來實作
class MvvmViewModel : ViewModel() {
val countState = MutableLiveData(1)
fun add(num: Int) {
countState.postValue(countState.value as Int + num)
}
fun reduce(num: Int) {
countState.postValue(countState.value as Int - num)
}
}
接下來我們就需要實作Screen,在Screen中將Content和ViewModel結合起來
@Composable
fun Screen1(
navController: NavController
) {
val viewModel: MvvmViewModel = viewModel()
val count by viewModel.countState.observeAsState(0)
Content1(count = count,
{ navController.navigate("screen2") }
) {
viewModel.add(1)
}
}
我們可以看到借助于viewModel()方法我們可以在jetpack compose中很方便快捷的創建viewModel,同時也可以將livedata方便的轉換為compose state,當state發生變化時,界面就會自動重組并顯示,比原有安卓view體系使用起來方便很多,
MVI
MVI架構大多數人可能不是很了解,不過其實也不是很難,它把mvvm的雙向系結變成單向資料流,強調資料的單向流動和資料源唯一性,state不可變,view通過state渲染資料,viewmodel通過action改變state

Content代碼和MVVM一樣
ViewModel代碼如下
class MVIViewModel : ViewModel() {
val viewState = MutableLiveData(ViewState())
val userIntent = Channel<UiAction>(Channel.UNLIMITED)
init {
handleAction()
}
private fun add(num: Int) {
viewState.value?.let {
viewState.postValue(it.copy(count = it.count + 1))
}
}
private fun reduce(num: Int) {
viewState.value?.let {
viewState.postValue(it.copy(count = it.count - 1))
}
}
private fun handleAction() {
viewModelScope.launch {
userIntent.consumeAsFlow().collect {
when (it) {
is UiAction.AddAction -> add(it.num)
is UiAction.ReduceAction -> reduce(it.num)
}
}
}
}
data class ViewState(val count: Int = 1)
sealed class UiAction {
class AddAction(val num: Int) : UiAction()
class ReduceAction(val num: Int) : UiAction()
}
}
Screen代碼如下
@Composable
fun Screen1(
navController: NavController
) {
val viewModel: MVIViewModel = viewModel(navController = navController)
val viewState by viewModel.viewState.observeAsState(MVIViewModel.ViewState())
val coroutine = rememberCoroutineScope()
Content1(count = viewState.count,
{ navController.navigate("screen2") }
) {
coroutine.launch {
viewModel.userIntent.send(MVIViewModel.UiAction.AddAction(1))
}
}
}
通過上訴代碼我們應該可以體會出mvvm和mvi之間的區別
多page通信問題
對于一個應用來說,通常不可能只會有一個page,由于mvvm和mvi的viewmodel都是和page系結的,對于多個page來說,要想實作跨page通信可能比較麻煩,這也是目前mvvm和mvi的一個大問題,不過這個問題也很好解決,我們可以定義一個方法可以獲取其他page的viewmodel或者全域的viewmodel即可,不過在compose中該如何實作,首先我們要了解compose中的viewmodel是如何保存的
通常compose都是和navigation來實作page跳轉的,對于上面的viewModel()方法,我們分析原始碼可以發現,每跳轉一個新的page,它都會新建一個新的ViewModelStoreOwner(即NavBackStackEntry),所以如果我們不指定ViewModelStoreOwner的話我們是獲取不到上一個page和全域的viewmodel的,因此我們可以提供一個創建viewModel的方法,在創建時候先去獲取當前路由堆疊和全域中存在的viewModel,獲取不到的話再新建或者拋一個例外出去,這樣就可以在page中獲取到其他page的viewModel,實作page間的通信了
代碼也非常簡單,如下
@Suppress("MissingJvmstatic")
@Composable
inline fun <reified VM : ViewModel> viewModelOfNav(
navController: NavController,
key: String? = null,
factory: ViewModelProvider.Factory? = null
): VM {
val javaClass = VM::class.java
var viewModelStoreOwner: ViewModelStoreOwner? = null
navController.backQueue.forEach {
if (it.existViewModel(javaClass, key = key)) {
viewModelStoreOwner = it
return@forEach
}
}
if (viewModelStoreOwner == null) {
val context = LocalContext.current
if (context is ViewModelStoreOwner && context.existViewModel(javaClass, key = key)) {
viewModelStoreOwner = context
}
}
return viewModel(
javaClass, viewModelStoreOwner = viewModelStoreOwner ?: checkNotNull(
LocalViewModelStoreOwner.current
) {
"No ViewModelStoreOwner was provided via LocalViewModelStoreOwner"
}, key = key, factory = factory
)
}
class NotExistException : Exception("not exist")
class ExistFactory : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
throw NotExistException()
}
}
fun <VM : ViewModel> ViewModelStoreOwner.existViewModel(
modelClass: Class<VM>,
key: String? = null
): Boolean {
var isExist = true
val provider = ViewModelProvider(this, ExistFactory())
try {
if (key != null) {
provider.get(key, modelClass)
} else {
provider.get(modelClass)
}
} catch (e: NotExistException) {
isExist = false
}
return isExist
}
其實到這里我們會發現,我們的MVI架構加上多page通信,有點類似于flutter的bloc架構了,同樣是單向資料流,只不過一個是sink和stream ,這里是action和state,bloc同樣可以通過provideof實作page間通信,所以我們分析下來,很多架構都是類似的
總結
今天我們簡單分析了原有的安卓開發架構MVVM和MVI和jetpack compose的結合以及適配性,我們發現兩者可以完美結合,甚至比原有安卓的view開發更加便捷高效,其實這個就是宣告式ui框架的好處,接下來,我還會繼續分析其他開發架構比如redux和bloc等和jetpack compose的結合,以及在compose中的實作
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/292984.html
標籤:其他
上一篇:Android動態代理
