Android Jetpack Compose 使用 ViewModel

1.ViewModel为啥不可或缺

我们已经了解到了rememberSavable可以在屏幕旋转,当前Activity被系统回收时保存状态。ViewModel正好也是干这个活的,那为啥没有使用rememberSavable替换ViewModel呢?比如我们在前面的文章中用到的计数器例子,可以使用remremberSavable来保存状态,也可以用viewModel来保存状态,但是这只是一个Demo而已,在真实的项目中,业务逻辑是不会只是简单的加加减减,往往会复杂得多得多,如果将代码全放到Stateful Composable中,会导致UI的职责不清晰,毕竟我们的Composable的主要职责是显示UI。

所以,在复杂的业务逻辑下,我们可以将Stateful的状态提到ViewModel中管理,这样Stateful Composable也就变成了一个Stateless Composable,通过参数传入不同的ViewModel即可替换具体的业务逻辑,大大增加了可复用性和可测试性

2 在Compose UI中使用ViewModel

借用前面的计数器例子,这里使用Compose UI 和ViewModel的配合,实现一个计数器例子,代码如下:

kotlin 复制代码
class ComposeCounterAct : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyComposeTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    TestCounter()
                }
            }
        }
    }

    @Composable
    fun TestCounter(){
        val viewModel:ComposeCounterViewModel = viewModel()
        CounterComponent(viewModel.counter.value,viewModel::increment,viewModel::decrement)
    }

    @Composable
    fun CounterComponent(
        counter: Int, // 重组时传入当前需要显示的计数
        onIncrement: () -> Unit,// 回调点击加号的事件
        onDecrement: () -> Unit // 回调单击减号的事件
    ) {
        Column(modifier = Modifier.padding(16.dp)) {
            Text(
                "$counter",
                Modifier.fillMaxWidth(),
                textAlign = TextAlign.Center
            )

            Row {
                Button(
                    onClick = { onDecrement() },
                    modifier = Modifier.weight(1f)
                ) {
                    Text("-")
                }

                Spacer(Modifier.width(16.dp))
                Button(
                    onClick = { onIncrement() },
                    modifier = Modifier.weight(1f)
                ) {
                    Text("+")
                }
            }
        }
    }
}

class ComposeCounterViewModel:ViewModel(){
    private val _counter = mutableStateOf(0)
    val counter: State<Int> = _counter

    fun increment(){
        _counter.value = _counter.value + 1
    }

    fun decrement(){
      if(_counter.value>0){
          _counter.value = _counter.value -1
      }
    }
}

如上面代码所示,在Compose UI 对于ViewModel的使用和传统的view基本相同,在Compose中viewModel()方法是一个Composable方法,它的作用是在Composable中创建ViewModel,TestCounter通过viewModel()方法创建了一个ComposeCounterViewModel类型的ViewModel,这个ViewModel持有计数器的状态。

注意:在Composable中使用viewModel()方法需要添加依赖: implementation('androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2') ,否则会提示方法找不到。

这里的viewModel()方法会从当前最近的ViewModelStore中获取ViewModel实例,这个ViewModelStore可能是一个Activity,也可能是一个Fragment,如果ViewModel实例不存在,就创建一个新的并且存入ViewModelStore。只要是ViewModelStore不销毁,其内部的ViewModel实例就会一直存活。例如一个Activity中的Composable通过viewModel()方法创建的ViewModel被当前的Activity所持有。在Activity销毁之前,ViewModel会一直存在,viewModel()的每次调用都会返回一个实例,所以我们不用remember缓存也可以。

注意: 调用viewModel()方法的Composable无法进行预览,若需要进行预览,可以从持有ViewModel的Composable中将需要预览的部分提取成StateLess组件,如文中的CounterComponent组件。

相关推荐
alexhilton10 小时前
生命周期感知的多模块启动模式
android·kotlin·android jetpack
hai_android21 小时前
RecyclerView 缓存机制详解
android·kotlin·android jetpack
hai_android1 天前
深入理解 Java 的四种引用:强引用、软引用、弱引用、虚引用
android·kotlin·android jetpack
zbmwa1 天前
jetpack compose 副作用 SideEffect
android jetpack
alexhilton7 天前
藏在设备上的秘密,终究藏不住
android·kotlin·android jetpack
传奇开心果编程9 天前
【Jetpack Compose基础语法学与练】第8课 rememberSaveable,页面旋转/系统重建保留状态
android·学习·ui·kotlin·android jetpack
传奇开心果编程9 天前
【Jetpack Compose基础语法学与练】第7课 if条件渲染 + 列表基础(LazyColumn)
学习·ui·kotlin·android jetpack
传奇开心果编程9 天前
【Jetpack Compose基础语法学与练】第9课 SideEffect副作用 + LaunchedEffect,处理异步任务和生命周期
android·学习·ui·kotlin·android jetpack
惟恋惜10 天前
第 3 章 · 列表、滚动与 Lazy 深入:告别 RecyclerView 思维
ui·android jetpack
传奇开心果编程11 天前
【Jetpack Compose基础语法学与练】第6课 TextField文本输入,字符串状态与输入交互
学习·前端框架·kotlin·android jetpack