class MvvmActivity : AppCompatActivity() {
private val mvvmViewModel = MvvmViewModel(DataRepository())
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposePlaygroundTheme {
MvvmApp(mvvmViewModel) //将vm传给Composable
}
}
}
}
Compose 项目一般使用单 Activity 结构, Activity 作为全局入口非常适合创建全局 ViewModel。子 Compoable 之间需要基于 ViewModel 通信,所以构建 Composable 时将 ViewModel 作为参数传入。
Sample 中我们在 Activity 中创建的 ViewModel 仅仅是为了传递给 MvvmApp 使用,这种情况下也可以通过传递 Lazy,将创建延迟到真正需要使用的时候以提高性能。
当涉及到 Compose 页面切换时,navigation-compose 是一个不错选择,Sample中也特意设计了SearchBarScreen 和 SearchResultScreen 的切换场景
// build.gradle
implementation “androidx.navigation:navigation-compose:$latest_version”
@Composable
fun MvvmApp(
mvvmViewModel: MvvmViewModel
) {
val navController = rememberNavController()
LaunchedEffect(Unit) {
mvvmViewModel.navigateToResults
.collect {
navController.navigate(“result”) //订阅VM路由事件通知,处理路由跳转
}
}
NavHost(navController, startDestination = “searchBar”) {
composable(“searchBar”) {
MvvmSearchBarScreen(
mvvmViewModel,
)
}
composable(“result”) {
MvvmSearchResultScreen(
mvvmViewModel,
)
}
}
}
-
在 root-level 的 MvvmApp 中定义 NavGraph, composable(“$dest_id”){} 中构造路由节点的各个子 Screen,构造时传入 ViewModel 用于 Screen 之间的通信
-
每个 Composable 都有一个 CoroutineScope 与其 Lifecycle 绑定,LaunchedEffect{} 可以在这个 Scope 中启动协程处理副作用。代码中使用了一个只执行一次的 Effect 订阅 ViewModel 的路由事件通知
-
当然我们可以将 navConroller 也传给 MvvmSearchBarScreen ,在其内部直接发起路由跳转。但在较复杂的项目中,跳转逻辑与页面定义应该尽量保持解耦,这更利于页面的复用和测试。
-
我们也可以在 Composeable 中直接 mutableStateOf() 创建 state 来处理路由跳转,但是既然选择使用 ViewModel 了,那就应该尽可能将所有 state 集中到 ViewModle 管理。
注意: 上面例子中的处理路由跳转的 navigateToResults 是一个“事件”而非“状态”,关于这部分区别,在后文在详细阐述
接下来看一下两个 Screen 的具体实现
@Composable
fun MvvmSearchBarScreen(
mvvmViewModel: MvvmViewModel,
) {
SearchBarScreen {
mvvmViewModel.searchKeyword(it)
}
}
@Composable
fun MvvmSearchResultScreen(
mvvmViewModel: MvvmViewModel
) {
val result by mvvmViewModel.result.collectAsState()
val isLoading by mvvmViewModel.isLoading.collectAsState()
SearchResultScreen(result, isLoading, mvvmViewModel.key.value)
}
大量逻辑都抽象到 ViewModel 中,所以 Screen 非常简洁
-
SearchBarScreen 接受用户输入,将搜索关键词发送给 ViewModel
-
MvvmSearchResultScreen 作为结果页显示 ViewModel 发送的数据,包括 Loading 状态和搜索结果等。
-
collectAsState 用来将 Flow 转化为 Compose 的 state,每当 Flow 接收到新数据时会触发 Composable 重组。Compose 同时支持 LiveData、RxJava 等其他响应式库的collectAsState
UI层的更多内容可以查阅 SearchBarScreen 和 SearchResultScreen 的源码。经过逻辑抽离后,这两个 Composable 只剩余布局相关的代码,可以在任何一种 MVX 中实现复用。
最后看一下 ViewModel 的实现
class MvvmViewModel(
private val searchService: DataRepository,
) {
private val coroutineScope = MainScope()
private val _isLoading: MutableStateFlow = MutableStateFlow(false)
val isLoading = _isLoading.asStateFlow()
private val _result: MutableStateFlow<List> = MutableStateFlow(emptyList())
val result = _result.asStateFlow()
private val _key = MutableStateFlow(“”)
val key = _key.asStateFlow()
//使用Channel定义事件
private val _navigateToResults = Channel(Channel.BUFFERED)
val navigateToResults = _navigateToResults.receiveAsFlow()
fun searchKeyword(input: String) {
coroutineScope.launch {
_isLoading.value = true
_navigateToResults.send(true)
_key.value = input
val result = withContext(Dispatchers.IO) { searchService.getArticlesList(input) }
_result.emit(result.data.datas)
_isLoading.value = false
}
}
}
-
接收到用户输入后,通过 DataRepository 发起搜索请求
-
搜索过程中依次更新 loading(loading显示状态)、navigateToResult(页面跳转事件)、 key(搜索关键词)、result(搜索结果)等内容,不断驱动UI刷新
所有状态集中在 ViewModel 管理,甚至页面跳转、Toast弹出等事件也由 ViewModel 负责通知,这对单元测试非常友好,在单测中无需再 mock 各种UI相关的上下文。
========================================================================
Jeptack 的意义在于降低 MVVM 在 Android平台的落地成本。
引入 Jetpack 后的代码变化不大,主要变动在于 ViewModel 的创建。
Jetpack 提供了多个组件,降低了 ViewModel 的使用成本:
-
通过 hilt 的 DI 降低 ViewModel 构造成本,无需手动传入 DataRepository 等依赖
-
任意 Composable 都可以从最近的 Scope 中获取 ViewModel,无需层层传参。
@HiltViewModel
class JetpackMvvmViewModel @Inject constructor(
private val searchService: DataRepository // DataRepository 依靠DI注入
) : ViewModel() {
…
}
@Composable
fun JetpackMvvmApp() {
val navController = rememberNavController()
NavHost(navController, startDestination = “searchBar”, route = “root”) {
composable(“searchBar”) {
JetpackMvvmSearchBarScreen(
viewModel(navController, “root”) //viewModel 可以在需要时再获取, 无需实现创建好并通过参数传进来
)
}
composable(“result”) {
JetpackMvvmSearchResultScreen(
viewModel(navController, “root”) //可以获取跟同一个ViewModel实例
)
}
}
}
@Composable
inline fun viewModel(
navController: NavController,
graphId: String = “”
): VM =
//在 NavGraph 全局范围使用 Hilt 创建 ViewModel
hiltNavGraphViewModel(
backStackEntry = navController.getBackStackEntry(graphId)
)
Jetpack 甚至提供了 hilt-navigation-compose 库,可以在 Composable 中获取 NavGraph Scope 或 Destination Scope 的 ViewModel,并自动依赖 Hilt 构建。Destination Scope 的 ViewModel 会跟随 BackStack 的弹出自动 Clear ,避免泄露。
// build.gradle
implementation androidx.hilt:hilt-navigation-compose:$latest_versioin
“未来 Jetpack 各组件之间协同效应会变得越来越强。” 参考
https://developer.android.com/jetpack/compose/libraries#hilt
===============================================================
MVI 与 MVVM 很相似,其借鉴了前端框架的思想,更加强调数据的单向流动和唯一数据源,可以看做是 MVVM + Redux 的结合。
MVI 的 I 指 Intent,这里不是启动 Activity 那个 Intent,而是一种对用户操作的封装形式,为避免混淆,也可唤做 Action 等其他称呼。用户操作以 Action 的形式送给 Model层 进行处理。代码中,我们可以用 Jetpack 的 ViewModel 负责 Intent 的接受和处理,因为 ViewModel 可以在 Composable 中方便获取。
在 SearchBarScreen 用户输入关键词后通过 Action 通知 ViewModel 进行搜索
@Composable
fun MviSearchBarScreen(
mviViewModel: MviViewModel,
onConfirm: () -> Unit
) {
SearchBarScreen {
mviViewModel.onAction(MviViewModel.UiAction.SearchInput(it))
}
}
通过 Action 通信,有利于 View 与 ViewModel 之间的进一步解耦,同时所有调用以 Action 的形式汇总到一处,也有利于对行为的集中分析和监控
@Composable
fun MviSearchResultScreen(
mviViewModel: MviViewModel
) {
val viewState by mviViewModel.viewState.collectAsState()
SearchResultScreen(
viewState.result, viewState.isLoading, viewState.key
)
}
MVVM 的 ViewModle 中分散定义了多个 State ,MVI 使用 ViewState 对 State 集中管理,只需要订阅一个 ViewState 便可获取页面的所有状态,相对 MVVM 减少了不少模板代码。
相对于 MVVM,ViewModel 也有一些变化
class MviViewModel(
private val searchService: DataRepository,
) {
private val coroutineScope = MainScope()
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
【附】相关架构及资料
往期Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术,群内还有技术大牛一起讨论交流解决问题。
1655682877)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-CME0jAjE-1711655682877)]
【附】相关架构及资料
[外链图片转存中…(img-RAGIoQLV-1711655682878)]
[外链图片转存中…(img-WCuEpv7t-1711655682878)]
往期Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术,群内还有技术大牛一起讨论交流解决问题。