Jetpack Compose 架构如何选?MVP 、 MVVM 还是 MVI,被裁半年考入编制内月薪6K

文章讲述了在Android应用中如何使用JetpackCompose和MVI/MVVM架构,介绍了ViewModel的创建、NavGraph的定义、以及如何在Composable中处理ViewModel的通信和状态管理。还讨论了HiltDI对ViewModel构造的帮助和MVI模式下Action的运用。
摘要由CSDN通过智能技术生成

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,将创建延迟到真正需要使用的时候以提高性能。

定义 NavGraph


当涉及到 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


接下来看一下两个 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 实现


最后看一下 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相关的上下文。

Jetpack MVVM

========================================================================


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

===============================================================


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移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

【附】相关架构及资料

往期Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术,群内还有技术大牛一起讨论交流解决问题。

1655682877)]
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-CME0jAjE-1711655682877)]

【附】相关架构及资料

[外链图片转存中…(img-RAGIoQLV-1711655682878)]

[外链图片转存中…(img-WCuEpv7t-1711655682878)]

往期Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术,群内还有技术大牛一起讨论交流解决问题。

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

  • 26
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值