Jetpack Compose 中的状态管理

网友投稿 1371 2022-10-06

Jetpack Compose 中的状态管理

Jetpack Compose 中的状态管理

文章目录

​​1. 概述​​​​2. 状态的更新 和 `remember` Api​​

​​2.1 remember api​​​​2.2 可观察对象 MutableState​​​​2.3 LiveData、Flow、RxJava​​​​2.4 配置变更后的状态保持​​

​​3. 状态提升​​​​4. 状态容器​​

​​4.1 构建普通状态容器​​​​4.2 ViewModel​​

​​5. 总结​​​​参考文章​​

1. 概述

因为 ​​Text​​​ 、 ​​TextButton​​​ 都是是被 ​​Composable​​ 修饰的可组合项,我们不能在一个可组合项中去引用另一个可组合项,那此我们如何更新 Text 中的文案呢?

Jetpack Compose 提供了一些状态的 API, 它关联了状态和这些可组合项,用于解决上面提出的问题。

2. 状态的更新 和 remember Api

Compose 只有唯一的更新手段:以新的参数重新调用可组合项,触发 Compose 重组。

请看下面官方代码:

@Composablefun HelloContent() { Column(modifier = Modifier.padding(16.dp)) { Text( text = "Hello!", modifier = Modifier.padding(bottom = 8.dp), style = MaterialTheme.typography.h5 ) OutlinedTextField( value = "", onValueChange = { }, label = { Text("Name") } ) }}

该页面所呈现的是静态的,没有任何反应,我们甚至不能在 ​​OutlinedTextField​​ 中输入文字:

Jetpack 提供了 ​​remeber​​ Api 用于存储属性, 它将对象存储在内存中。

2.1 remember api

在初始组合期间(即初始化页面),​​remember​​ 计算的值会存储在该组合中,并且在重组期间,会返回存储的值。

创建用法如下:

val name: String = remember { "rikka" }

​​remember​​ 函数会返回一个可组合项,它会缓存我们在 lambda 表达式里面创造的值,因为它可组合的属性,所以它可以做为组合的状态而存在。

2.2 可观察对象 MutableState

上面的代码中,使用 ​​remember​​​ 创造出来的状态是不可变的, 但是大部分情况下状态是可变的,所以为了引入可变的机制, Compose 提供了一个可观察模型: ​​MutableState​​ , 它能够在值发生变化时更新UI。

interface MutableState : State { override var value: T}

​​MutableState​​​ 是一个可变值的持有者, 在执行 ​​Composeable​​​ 函数期间会去读取 ​​value​​​ 值,当前的 ​​RecomposeScope​​​ 将会订阅这个值的读写。当更改 ​​value​​​ 属性时,将会通知任何订阅了该值的 ​​RecomposeScope​​,就会触发重组。

这里注意:如果 value 被更改了,但是更改前后属性一样,则不会发生重组。

我们可以使用 ​​mutableStateOf​​​ 来创建它,并且用 ​​remember​​ 包装它,以便在可组合项中使用, 代码如下:

val name: MutableState = remember { mutableStateOf("rikka") }// 状态的更新:Text( modifier = Modifier.clickable { name.value = "vera" }, text = name.value )

我们可以使用属性委托, 使用 ​​by​​​ 来解包 ​​MutableState​​:

var name: String by remember { mutableStateOf("rikka") }Text( modifier = Modifier.clickable { name = "vera" }, text = name)

2.3 LiveData、Flow、RxJava

在 Android 中,我们往往在逻辑层使用 ​​LiveData​​​、 ​​Flow​​​ 做为可观察对象, 而非 ​​MutableState​​,也就是说 LiveData、 Flow、 Observable 不能够直接往 Compose 上套。这该怎么办呢? 难道要用 MutableState 再去观察 LiveData?

Jetpack 提供了这种支持,它支持上述几个常用的可观察模型转化成 ​​MutableState​​。

​​LiveData​​​ 转化成​​MutableState​​​,使用​​observeAsState​​:

// ViewModelprivate val _name = MutableLiveData("rikka")val name: LiveData get() = _name// UI 层val name by viewModel.name.observeAsState()Text( text = name ?: "",)

​​Flow​​​ 转化成​​MutableState​​​, 使用​​collectAsState​​:

// ViewModelprivate val _name = MutableStateFlow("rikka")val name: StateFlow get() = _name// UI 层val name by viewModel.name.collectAsState()Text( text = name,)

Rx 转化成​​MutableState​​​,使用​​subscribeAsState​​:

val name: String by observable.subscribeAsState("rikka")Text( text = name)

这样我们就可以继续在代码中使用 LiveData、Flow ,而不用担心它们无法和 Compose 进行联动了。

2.4 配置变更后的状态保持

有时候我们的配置可能会变更,例如典型的翻转屏幕,这样的话使用原有的 ​​remember​​ 存储会丢失,从而重置状态。

所以 Jetpack 提供了 ​​rememberSaveable​​​ 来解决这个问题, 它内部使用 Bundle 来缓存状态,使得在配置更改后,依然能够保持状态, 就像 Activity 的 ​​onSaveInstanceState​​ 一样, 使用方法也很简单:

var name: String by rememberSaveable { mutableStateOf("rikka") }

3. 状态提升

根据上面所述, 可组合项是分为有状态的,和没有状态的。

这会有一个问题: 有状态的可组合项,虽然可以变化,但是不易被其它地方复用,也更加难测试,这有点像纯函数和非纯函数的区别了。

而 Compose 提出了一种“状态提升”的概念,将带有状态的可组合项分成两个可组合项:

可组合项 A一般情况下存储两个参数:①:​​​value: T​​​, 即当前状态②:​​​onValueChange: (T) -> Unit​​​ ,状态改变时的回调,建议​​T​​​ 是新值将两个调用可组合项 B可组合项 B:将​​​value​​​ 、​​onValueChange​​ 这些状态信息做为参数,自己避免存储状态, 即状态就是自己的状态

例如这是一个带状态的可组合项:

@Composablefun HelloContent() { Column(modifier = Modifier.padding(16.dp)) { var name by rememberSaveable { mutableStateOf("rikka") } // 1. 状态 if (name.isNotEmpty()) { Text( text = "Hello, $name!", modifier = Modifier.padding(bottom = 8.dp), style = MaterialTheme.typography.h5 ) } OutlinedTextField( value = name, onValueChange = { name = it }, // 2. 状态改变的回调 label = { Text("Name") } ) }}

由于注释1、2 的存在, ​​HelloContent​​ 函数不好测试和复用,所以这里使用状态提升,优化后的代码如下所示:

// 可组合项 A@Composablefun HelloContent() { var name by rememberSaveable { mutableStateOf("") } HelloContent(name = name, onNameChange = { name = it })}// 可组合项 B@Composablefun HelloContent(name: String, onNameChange: (String) -> Unit) { Column(modifier = Modifier.padding(16.dp)) { Text( text = "Hello, $name", modifier = Modifier.padding(bottom = 8.dp), style = MaterialTheme.typography.h5 ) OutlinedTextField( value = name, onValueChange = onNameChange, label = { Text("Name") } ) }}

上面代码中,可组合项 A 就是把状态“提上来”的函数。 可组合项B更加容易被复用和测试。

状态提升,往好的说就是拆出一个纯函数、提高UI或状态的复用度(解耦了), 往不好的说就不过就是重载、化简为繁。

如果你认为你的可组合项、状态不会被复用,或者至少短时间内不会被其他人使用,也没有做状态提升的必要性。

4. 状态容器

我们可以使用可组合项来存储状态,就和上面讲到的那样。

但是在更为复杂的情况下,例如有多个不同来源的状态的场景,仅仅是使用状态提升,可读性不会这么好,而且难以符合单一可信原则,所以 Compose 又提供了额外两种解决方案:

构建普通状态容器构建一个数据类和可组合项,用于委托一个可组合项所有的状态和逻辑 (属于状态提升的升级版)把状态移到 ViewModel 中去使用 ViewModel 和额外的数据类, 来委托页面级的 UI 所有的状态和逻辑

上面两者的区别是: 普通状态容器管理的 UI 是小的、微观的,仅仅针对一个可组合项; 而 ViewModel 管理的 UI 则是大的、宏观的,可能针对 n 个可组合项的, 其次,它的生命周期更长一点。

4.1 构建普通状态容器

例如,我们的一个 MyApp 的可组合项:

@Composablefun MyApp() { ComposeTheme { val scaffoldState = rememberScaffoldState() // 1 val shouldShowBottomBar = shouldShowBottomBar() //2 Scaffold( scaffoldState = scaffoldState, bottomBar = { if (shouldShowBottomBar) { BottomBar( tabs = BTTOM_BARS_TAB, // 3 navigateToRoute = { navigateToBottomBarRoute(it) // 4 } ) } } ) { NavHost(navController = navController, "initial") { /* ... */ } // 5 } }}

在上面可组合项 ​​MyApp​​,有着注释1、2、3 处的状态,和注释4、5处的行为逻辑, 很明显,使用状态提升也挺麻烦,可读性不好,看起来很绕,因为提升后的函数可能会有5个参数。

下面来构建普通状态容器,我们先是构建一个数据类,专门存放 ​​MyApp​​ 所有的UI状态和行为:

class MyAppState( val scaffoldState: ScaffoldState, val navController: NavHostController, /* ... */) { val bottomBarTabs = ... val shouldShowBottomBar: Boolean get() = /* ... */ fun navigateToBottomBarRoute(route: String) { /* ... */ }}

接下来,使用 ​​rember​​ Api ,给该数据类赋予在可组合项中存储的能力:

@Composablefun rememberMyAppState( scaffoldState: ScaffoldState = rememberScaffoldState(), navController: NavHostController = rememberNavController(), /* ... */) = remember(scaffoldState, navController, /* ... */) { MyAppState(scaffoldState, navController, /* ... */)}

最后优化 ​​MyApp​​

@Composablefun MyApp() { MyTheme { val myAppState = rememberMyAppState() Scaffold( scaffoldState = myAppState.scaffoldState, bottomBar = { if (myAppState.shouldShowBottomBar) { BottomBar( tabs = myAppState.bottomBarTabs, navigateToRoute = { myAppState.navigateToBottomBarRoute(it) } ) } } ) { NavHost(navController = myAppState.navController, "initial") { /* ... */ } } }}

优化后的 ​​MyApp​​​ 注重于 UI 的构建,所有的状态和行为都委托给了 ​​MyAppState​​​,通过和 ​​rememberMyAppState​​ 配套,解耦了UI和逻辑。

4.2 ViewModel

ViewModel 因为其本身的特性,所以也是一个存储页面状态和逻辑的好容器。 它更适合存储屏幕级的状态和逻辑(宏观的),例如 ​​UiState​​。

举个例子, 我们有下面的 UiState,用于表示页面状态:

data class ExampleUiState( val dataToDisplayOnScreen: List = emptyList(), val userMessages: List = emptyList(), val loading: Boolean = false)

那么在 ViewModel 中可以这样写:

class ExampleViewModel( private val repository: MyRepository, private val savedState: SavedStateHandle) : ViewModel() { var uiState by mutableStateOf(ExampleUiState()) // 这里无论是 LiveData、Flow 还是 Rx,都可以转化成 MutableState private set // Business logic fun somethingRelatedToBusinessLogic() { /* ... */ }}

最后 UI 层的使用:

@Composablefun ExampleScreen(viewModel: ExampleViewModel = viewModel()) { val uiState = viewModel.uiState /* ... */ ExampleReusableComponent( someData = uiState.dataToDisplayOnScreen, onDoSomething = { viewModel.somethingRelatedToBusinessLogic() } )}@Composablefun ExampleReusableComponent(someData: Any, onDoSomething: () -> Unit) { /* ... */ Button(onClick = onDoSomething) { Text("Do something") }}

5. 总结

Compose 的可组合项是可以带有状态和行为的,为了更新 UI,就要以不同的状态(参数)去重新调用一边可组合项为了方便让我们 改变状态 触发 改变UI, Compose 提供了​​remember​​​ api 和​​MutableState​​,两者的联动,可以让我们轻松的构造可观察状态,即像 MVVM 那样, ViewModel 的变化可以(自动)更新 V 层​​remember​​ 提供了一些 api,可以帮助 Android MVVM 架构更好地和 Compose 结合, 例如提供把 Flow、 LiveData、Rx 转化成 MutableState 的能力Compose 有四种可组合项状态管理的方式①躺平型: 所有的 状态、逻辑都放在其可组合项里, 这会导致可组合项臃肿、 可读性低、复用能力差②状态提升: 将所有的状态、逻辑放在一个可组合项A中,去调用可组合项B, 可组合项B的状态、行为就是其参数。 这样的话, 可组合项B就是无状态、可复用的了, 而状态、行为的管理放在了可组合项 A 中③构建普通状态容器: 将一个可组合项所有的状态、逻辑放在一个数据类里面,通过​​remember​​​ api 赋予其存储能力,这样可以管理一个有复杂状态、逻辑的可组合项④ViewModel 管理状态:将多个可组合项所有的状态、逻辑放在一个数据类里面,通过​​remember​​​ api 赋予其存储能力,相较于上面更加宏观,可以管理整个 UI 的状态,例如​​UiState​​ 属性

参考文章

​​官网:状态和 Jetpack Compose​​

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:【音视频】srs直播平台搭建
下一篇:关于微信小程序底部导航栏目的开发(小程序子页面底部导航)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~