前言
ViewModel
和 remember
是 Jetpack Compose 中用于管理数据的两种不同机制。
它们有以下区别:
生命周期管理:
ViewModel
:ViewModel
是一个用于存储和管理与界面相关的数据的类,其生命周期与其关联的ViewModelStoreOwner
相关联(通常是Activity
或Fragment
)。这意味着
ViewModel
中的数据会在相关联的ViewModelStoreOwner
存在时保持状态,直到它们的生命周期结束。remember
:remember
是一个用于存储短暂数据的 Compose 状态管理器。它的生命周期与调用它的组件相关联,通常是函数组件。当组件被重新创建时,
remember
中存储的数据会丢失。
数据共享:
ViewModel
:ViewModel
通常用于存储与界面相关的持久性数据,它可以在多个组件之间共享,比如在同一个Activity
中的不同Fragment
之间共享数据。remember
:remember
主要用于存储临时性的局部状态,例如 UI 状态、临时缓存等,它的作用范围通常限制在调用它的组件内部。
数据持久性:
ViewModel
:ViewModel
中的数据通常具有较长的生命周期,并且在配置更改(如屏幕旋转)时会被保留。remember
:remember
中的数据通常是临时性的,不会在配置更改后保留。
用法:
ViewModel
: 通常通过在Activity
或Fragment
中使用ViewModelProvider
获取ViewModel
实例,并在需要时观察ViewModel
中的 LiveData 或使用它提供的数据。remember
:remember
可以直接在 Compose 组件内部使用,通过调用remember { }
或rememberSaveable { }
来创建和存储状态。
总的来说:
ViewModel
适合用于管理持久性数据和在不同组件之间共享数据。remember
则适合用于管理短暂的 UI 状态和局部状态。
remember/rememberSaveable
在Compose中,remember
和rememberSaveable
都是用于保存可组合函数的状态的方法,但它们在如何保存状态以及在什么情况下会重新计算状态上有所不同。
remember
: 这个函数在组合函数的生命周期内始终保持相同的状态。这意味着,每次组合函数重新调用时,它都会使用先前保存的状态值,而不会重新计算它。这对于静态数据或者不会因用户交互而改变的数据很有用。如果状态的改变不需要在组件生命周期之外持久化,remember
是一个更轻量级的选择。
1 | var password by remember { mutableStateOf("") } |
rememberSaveable
: 这个函数也会保存状态,但它还会将状态持久化,以便在应用程序进入后台或被销毁后,能够恢复该状态。这对于需要跨配置更改(例如旋转屏幕)或者应用程序生命周期的状态非常有用。它会将状态保存在Bundle
中,以确保状态的持久化。
1 | var password by rememberSaveable { mutableStateOf("") } |
因此,rememberSaveable
提供了对状态的持久化支持,而remember
则仅在组件生命周期内保存状态。
选择使用哪种取决于您需要的状态是否需要在应用程序重新启动后保持不变。
mutableStateOf/mutableStateListOf
mutableStateOf
是 Jetpack Compose 中的一个函数,用于创建可变的状态。
它的作用是创建一个可以被修改的状态,并且当状态发生改变时,Compose 会重新计算并更新相关的 UI。
具体来说,mutableStateOf
函数接受一个初始值作为参数,并返回一个包含该初始值的 MutableState
对象。
MutableState
对象具有 value
属性,可以读取和修改该状态的值。
当 MutableState
对象的值发生改变时,Compose 会根据新的状态重新计算 UI,以确保 UI 反映最新的状态。
例如,假设我们有一个 mutableStateOf
对象来表示一个计数器的值:
1 | val countState = remember { mutableStateOf(0) } |
然后我们可以通过修改 countState.value
的值来更新计数器的状态:
1 | countState.value += 1 |
每当 countState.value
的值发生改变时,与该状态相关联的 UI 将会重新计算并更新,从而反映最新的计数器值。
总的来说:
mutableStateOf
的作用是在 Jetpack Compose 中创建可变的状态,以便动态更新 UI,并确保 UI 反映最新的状态值。
ViewModel
ViewModel 类是一种业务逻辑或屏幕级状态容器。它用于将状态公开给界面,以及封装相关的业务逻辑。
它的主要优点是,它可以缓存状态,并可在配置更改后持久保留相应状态。这意味着在 activity 之间导航时或进行配置更改后(例如旋转屏幕时),界面将无需重新提取数据。
添加类
1 | class MyViewModel : ViewModel() { |
这时候我们每次初始化的时候都会是一个新的对象
1 | val mainViewModel:MyViewModel = MyViewModel() |
这样自定义组件时使用数据的时候复用的时候就不方便,能不能让我们的ViewModel
的实例在一个类中是同一个实例呢?
是可以的。
添加引用
1 | implementation ("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1") |
这时候我们使用实例的时候这样写
1 | val mainViewModel:MyViewModel = viewModel() |
注意 :
viewModel()
方法会根据类型和所在的ViewModelStoreOwner
自动生成和缓存实例。
viewModel
的实例是与ViewModelStoreOwner
(通常是一个Activity
或Fragment
)相关联的,这意味着ViewModel
的生命周期跟它的ViewModelStoreOwner
是相匹配的。
列表示例
ViewModel
1 | class MyViewModel : ViewModel() { |
数据初始化也可以
1 | val listItems = mutableStateListOf("张三","李四","王五") |
组件
1 |
|
数据更新的时候SideEffect
并没有触发。
SideEffect
中的回调函数只在 MyList
组件第一次创建时被调用,因为 SideEffect
会在组件创建时运行其代码块,并在每次组件重新组合时运行。
但是在这种情况下,MyList
组件在 mList
改变时并不会重新组合,因为 Compose 无法检测到列表数据的更改。
要使 SideEffect
在列表数据改变时被调用,可以考虑将列表数据作为 key
参数传递给 MyList
组件,这样当列表数据改变时,MyList
组件将会重新创建,触发 SideEffect
的回调函数。
例如:
1 |
|
在这个修改后的 MyList
中,我们使用了 key
参数将列表数据传递给组件。这样,当列表数据发生变化时,key
值也会变化,从而触发 MyList
的重新组合,使 SideEffect
得以再次执行。
使用
1 | Box() { |
注意
ViewModel中的数据必须在UI线程中更新才会出发页面刷新。
常见错误
接口请求
Reading a state that was created after the snapshot was taken or in a snapshot that has not yet been applied
更新数据在LaunchedEffect
1 | val appViewModel: AppViewModel = viewModel() |