Jetpack Compose 状态管理
点击关注公众号,“技术干货”及时达!Jetpack Compose 是用于构建原生 Android 界面的新工具包,状态管理对于构建健壮,高效且可维护的应用程序至关重要,理解和应用有效的状态管理模式,是充分利用 Jetpack Compose 能力的关键。
remember + MutableState使用 remember 可以将对象存储在内存中,系统会在初始组合期间将由 remember 计算的值存储在组合中,并在重组期间返回存储的值。
mutableStateOf 会创建可观察的 MutableState,是与 Compose 运行时集成的可观察类型,如果 value 有任何变化,系统就会为读取 value 的所有可组合函数安排重组。
在可组合项中声明 MutableState 对象的方法有三种:
valmutableState=remember{mutableStateOf(default)}
varvaluebyremember{mutableStateOf(default)}
val(value,setValue)=remember{mutableStateOf(default)}
计数器案例
@Composable
funCounter(){
varcountbyremember{mutableStateOf(1)}
Button(onClick={count++}){
Text(text=count.toString())
}
}
增加列表的点击效果案例
@Composable
funList(dataList:ListString){
valselectedStates=remember{
dataList.map{mutableStateOf(false)}
}
LazyColumn(content={
itemsIndexed(dataList){index,s-
valisSelect=selectedStates[index]
Text(text=s,modifier=Modifier.selectable(
selected=isSelect.value,
onClick={isSelect.value=!isSelect.value}
))
}
})
}
remember 还可以接受 key 参数,当 key 发生变化,缓存值会失效并再次对 lambda 块进行计算。
@Composable
inlinefunremember(
key1:Any?,
crossinlinecalculation:@DisallowComposableCalls()-T
):T{
returncurrentComposer.cache(currentComposer.changed(key1),calculation)
}
MutableState + ViewModelmutableStateOf 也可以直接在 ViewModel 中使用,mutableStateOf 是线程安全的,也能够保证状态的更新能通知到观察者,即 Composable 函数。
mutableStateOf 在 Composable 函数中使用需要搭配 remember 来保持状态,但是在 ViewModel 中使用却不用,因为 ViewModel 本身就可以缓存状态,并可在配置更改后持久保留相应状态。
classMainViewModel:ViewModel(){
varcountbymutableStateOf(1)
privateset
funincrease(){
count++
}
}
@Composable
funCounter(){
valviewModel:MainViewModel=viewModel()
Button(onClick={viewModel.increase()}){
Text(text=viewModel.count.toString())
}
}
其中,使用 viewModel() 函数需要引入依赖:
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2")
StateFlow 管理状态使用 StateFlow 来管理状态,一般与 ViewModel 结合使用,并在 Composable 函数中通过 collectAsState 收集状态,从而实现响应式 UI 更新。
classMainViewModel:ViewModel(){
privateval_dataFlow=MutableStateFlowListString(emptyList())
valdataFlow:StateFlowListString=_dataFlow
fungetData(){
valdataList=
arrayListOf("hello","google","android","apple","ios","huawei","harmony")
_dataFlow.value=dataList
}
}
点击按钮,获取数据。
@Composable
funList(){
valviewModel:MainViewModel=viewModel()
valdataListbyviewModel.dataFlow.collectAsState()
Column{
Button(onClick={viewModel.getData()}){
Text(text="GetData")
}
LazyColumn(content={
items(dataList){
Text(text=it)
}
})
}
}
LiveData 管理状态LiveData 是一种具有生命周期感知能力的可观察的数据存储器类,一般与 ViewModel 搭配使用,在 Compose 中使用 LiveData 进行状态管理,通过 observeAsState 转换为 Compose 可用的状态。
使用 observeAsState 需要引入依赖:
implementation("androidx.compose.runtime:runtime-livedata:1.4.0")
与上面 StateFlow 的代码案例类似,这里简单修改一下即可。
classMainViewModel:ViewModel(){
privateval_liveData=MutableLiveDataListString(emptyList())
valliveData:LiveDataListString=_liveData
fungetData(){
valdataList=
arrayListOf("hello","google","android","apple","ios","huawei","harmony")
_liveData.value=dataList
}
}
@Composable
funList(){
valviewModel:MainViewModel=viewModel()
valdataListbyviewModel.liveData.observeAsState(emptyList())
Column{
Button(onClick={viewModel.getData()}){
Text(text="GetData")
}
LazyColumn(content={
items(dataList){
Text(text=it)
}
})
}
}
状态提升状态提升用于在组合界面中管理和共享状态,可以更好地组织和管理应用程序的状态,并确保状态的一致性。状态提升的基本思想是将状态从子组件移动到父组件,并通过参数传递给子组件,状态向下流动,事件向上传播。
@Composable
fun Counter() {
var count by remember { mutableStateOf(1) }
CounterPage(count = count) {
count++
}
}
@Composable
fun CounterPage(count: Int, increase: () - Unit) {
Button(onClick = increase) {
Text(text = count.toString())
}
}
状态提升的好处:
「集中管理状态」:状态提升可以将状态集中管理在顶层组件中,使得状态更易于追踪和管理。「状态一致性」:通过将状态提升到顶层组件并向下传递,可以确保整个应用程序中的各个组件都使用相同的状态。「可重用性」:将状态从子组件移动到父组件后,子组件变得更加通用,因为它们不再依赖于特定的状态,这样可以促进组件的复用。「逻辑分离」:状态提升有助于将 UI 逻辑与状态管理逻辑分离开来,使得组件更加专注于 UI 渲染和交互,这种分离有助于代码模块化和降低代码的耦合度。附带效应附带效应是指发生在可组合函数作用域之外的应用状态的变化,如果需要更改应用的状态,应该使用 Effect API,以便以可预测的方式执行这些附带效应。
虽然 mutableStateOf 提供了一种方便的方式来跟踪 Compose 中的可变状态,但它本身并不支持执行副作用操作。这意味着如果需要在 Composable 函数内部执行具有副作用的操作如网络请求,文件读写等,则需要使用附带效应。
LaunchedEffectLaunchedEffect 用于在可组合项的作用域内运行挂起函数,当 LaunchedEffect 进入组合时,它会启动一个协程,并将代码块作为参数传递,如果 LaunchedEffect 退出组合,协程将取消。
@Composable
funEffect(){
valcount=remember{mutableStateOf(1)}
LaunchedEffect(Unit){
while(true){
delay(1000)
count.value++
}
}
Text(text="${count.value}")
}
rememberCoroutineScopeLaunchedEffect 只能在可组合函数中使用,如果需要在可组合项外启动协程, 就可以使用 rememberCoroutineScope
@Composable
funEffect(){
valcount=remember{mutableStateOf(1)}
valcoroutineScope=rememberCoroutineScope()
Column{
Button(onClick={
coroutineScope.launch{
delay(1000)
count.value++
}
}){
Text(text="${count.value}")
}
}
}
rememberUpdatedState当其中一个键参数发生变化时,LaunchedEffect 会重启。不过,有时可能希望在效应中捕获某个值,但如果该值发生变化,又不希望效应重启。为此,就需要使用 rememberUpdatedState 来创建对可捕获和更新的该值的引用,可以确保在效应中读取的状态是最新的。
举个例,先来看没有使用 rememberUpdatedState 的情况:
@Composable
funNewCounter(){
varcountbyremember{
mutableStateOf(0)
}
Column{
Button(onClick={count++}){
Text(text="increase:$count")
}
DelayText(count.toString())
}
}
@Composable
funDelayText(text:String){
vardelayTextbyremember{
mutableStateOf("")
}
LaunchedEffect(Unit){
delay(3000)
delayText=text
}
Text(text="DelayText:$delayText")
}
在3秒内连续点击按钮使其值增加,但是实际的结果并没有拿到最新的值,如下所示:
image.png为什么会出现这种情况呢?因为如果 key 没有变化的话,那么 LaunchedEffect 内部的 lambda 一直都是最初的那个实例,那个实例拿到的 text 就是最初刚启动的值。
这种情况就可以使用 rememberUpdateState 来解决,下面来改写一下 DelayText 函数。
@Composable
funDelayText(text:String){
vardelayTextbyremember{
mutableStateOf("")
}
valupdateTextbyrememberUpdatedState(newValue=text)
LaunchedEffect(Unit){
delay(3000)
delayText=updateText
}
Text(text="DelayText:$delayText")
}
运行效果:
image.png这就是 rememberUpdateState 存在的意义,说白了,就是用一个容器装着值,我们取的也依旧是最初的那个容器,但是这并不影响,因为取的不是容器本身,而是容器里面的值。
DisposableEffect对于需要在键发生变化或可组合项退出组合后进行清理的附带效应,可以使用 DisposableEffect。DisposableEffect 必须添加一个 onDispose 方法 ,此方法一般用来处理资源清理与释放。这里举例 Compose 官方文档的监听生命周期的例子:
@Composable
funHomeScreen(
lifecycleOwner:LifecycleOwner=LocalLifecycleOwner.current,
onStart:()-Unit,
onStop:()-Unit
){
valcurrentOnStartbyrememberUpdatedState(onStart)
valcurrentOnStopbyrememberUpdatedState(onStop)
DisposableEffect(lifecycleOwner){
valobserver=LifecycleEventObserver{_,event-
if(event==Lifecycle.Event.ON_START){
currentOnStart()
}elseif(event==Lifecycle.Event.ON_STOP){
currentOnStop()
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose{
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
}
classMainActivity:ComponentActivity(){
overridefunonCreate(savedInstanceState:Bundle?){
super.onCreate(savedInstanceState)
setContent{
StateComposeAppTheme{
Surface(
modifier=Modifier.fillMaxSize(),
color=MaterialTheme.colorScheme.background
){
Column{
HomeScreen(LocalLifecycleOwner.current,{
//执行onStart
},{
//执行onStop
})
}
}
}
}
}
}
SideEffect使用 SideEffect 可保证效果在每次成功重组后都会执行,它能正确的向外传递状态,可用于将 Compose 状态发布到非 Compose 代码,但不能用来处理耗时和异步任务。
@Composable
funCounter(){
varcountbyremember{mutableStateOf(1)}
SideEffect{
//每次重组都会触发,在此调用非 Composable 函数 handleCount 处理这个 count 的值。
handleCount(count)
}
Text(text=count.toString())
Button(onClick={count++}){
Text(text="increase")
}
}
produceStateproduceState 会启动一个协程,使用此协程可以将非 Compose 状态转换为 Compose 状态。produceState 可以在 Compose 中创建一个可观察的状态,并且可以通过协程来对其进行更新。这样就可以使用常规的 Kotlin 代码来管理应用程序状态,并且状态更改时,Compose 会自动重新绘制与该状态相关联的 UI 部分。
varcount=0
@Composable
funStateCounter(){
valcountState=produceState(initialValue=count){
while(true){
delay(1000)
value++
}
}
Column(
modifier=Modifier.fillMaxSize(),
verticalArrangement=Arrangement.Center,
horizontalAlignment=Alignment.CenterHorizontally
){
Text(text=countState.value.toString())
}
}
derivedStateOfderivedStateOf 可以将一个或多个状态对象转换为其他状态,它可以根据其他状态的变化来计算新的状态,同时确保只有在相关状态发生变化时才进行重新计算,从而提高性能。
@Composable
funCounter(){
varcountbyremember{mutableStateOf(1)}
valbeyondTenbyremember{
derivedStateOf{
count10
}
}
Column{
Text(text="Does it exceed 10:$beyondTen")
Button(onClick={count++}){
Text(text=count.toString())
}
}
}
snapshotFlow使用 snapshotFlow 将 State对象转换为冷 Flow,它会在收集到块时运行该块,并发出从块中读取的 State 对象的结果。
@Composable
funCounter(){
varcountbyremember{mutableStateOf(1)}
valcountFlow=snapshotFlow{count}
LaunchedEffect(Unit){
countFlow.collect{
//todo这里拿到值做些处理
}
}
Column{
Text(text=count.toString())
Button(onClick={count++}){
Text(text="increase")
}
}
}
重启效应Compose 中有一些效应如 LaunchedEffect,produceState,DisposableEffect等,会采用可变数量的参数和键来取消运行效应,并使用新的键启动一个新的效应。
比如 LaunchedEffect,它有个参数 key,用于标识启动的效果,确保在 key 改变时重新启动效果,而在 key 保持不变时不会重新启动效果。
@Composable
@NonRestartableComposable
@OptIn(InternalComposeApi::class)
funLaunchedEffect(
key1:Any?,
block:suspendCoroutineScope.()-Unit
){
valapplyContext=currentComposer.applyCoroutineContext
remember(key1){LaunchedEffectImpl(applyContext,block)}
}
@Composable
funCounter(){
varcountbyremember{mutableStateOf(1)}
LaunchedEffect(count){
//todocount每次变化时都会执行
}
Column{
Text(text=count.toString())
Button(onClick={count++}){
Text(text="increase")
}
}
}
也可以使用常量作为键,使其遵循调用点的生命周期。举个例子:
@Composable
funCounter(){
varcountbyremember{mutableStateOf(1)}
LaunchedEffect(true){
//只执行一次
}
Text(text=count.toString())
Column{
Button(onClick={count++}){
Text(text="increase")
}
}
}
因为 LaunchedEffect 的 key 参数是一个常量 true,所以它的副作用会在组合第一次被计算时执行,之后不会再次执行。
点击关注公众号,“技术干货”及时达!
阅读原文
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。 项目经理在线