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的配合,实现一个计数器例子,代码如下:

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组件。

GitHub 加速计划 / compose / compose
33.28 K
5.15 K
下载
compose - Docker Compose是一个用于定义和运行多容器Docker应用程序的工具,通过Compose文件格式简化应用部署过程。
最近提交(Master分支:3 个月前 )
5e3a0953 full diff: https://github.com/docker/cli/compare/v27.4.0-rc.1...8d1bacae3e49ed1d096eede8eef4ae851d7f2eae Signed-off-by: Sebastiaan van Stijn <github@gone.nl> 9 天前
a2a3eb72 - full diff: https://github.com/docker/cli/compare/cb3048fbebb1...v27.4.0-rc.1 Signed-off-by: Sebastiaan van Stijn <github@gone.nl> 9 天前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐