核心 ViewModel处理数据[服务器]的获取、整理、[服务器]更新 +UI响应+[内聚],ViewModel是变化的Presenter,不过Compose内涵了响应式编程,其实就是数据绑定
MVVM框架
jetpack compose的 MVVM模式 其实就是用Viewmode代替了 presenter,同时配套提供了相应的数据驱动,响应式编程的一些工具类,比如MutableStateFlow,类似LiveData等。
基本使用
class MainViewModel: ViewModel() {
private val _stateFlow= MutableStateFlow("Hello World")
val stateFlow = _stateFlow.asStateFlow()
<!--相应UI交互的相应函数-->
fun onEvent(event: Event) {
...
<!--定义事件-->
sealed interface Event {
data object Stopped : Event
data object Resume : Event
}
<!--定义数据-->
data class UiState(
val msgIndex:Int = 0,
)
}
MutableStateFlow可以在初期看做是LiveData的替代,其实就是另一种为了感知数据变化的数据组件。_stateFlow与_stateFlow.asStateFlow()一个为了对内,一个对外,对内可修改,对外只读且响应式呈现UI,这样可以将数据修改全部内聚在ViewModel里面。源码里可以看到会被MutableStateFlow封装。
public fun <T> MutableStateFlow<T>.asStateFlow(): StateFlow<T> =
ReadonlyStateFlow(this, null)
UI 交互如何影响ViewMode呢,其实是通过事件来完成,用发消息,替换掉了之前Presenter的直接处理
@Composable
fun MainScreen(
state: MainViewModel .UiState,//这里传入的一般是被rember封装过的数据
sendEvent: (event: MainViewModel.Event) -> Unit,
)
如何使用
// viewmode注入,类似presenter new
val viewModel = hiltViewModel<MainViewModel>()
//获取remember 的 uiState,如果需要感知声明周期,可以用其他函数
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
//构造界面 注意这里传入 uiState,其实是已经被remember封装过的 uiState,具备响应式能力
MainScreen( state = uiState, sendEvent = viewModel::onEvent )
可以看出 MutableStateFlow并不具备记忆能力,通过collectAsStateWithLifecycle才行,其实对于普通业务开发而言,两者都能完成需求,StateFlow更灵活而已。
remember用法 :compose函数,用来记录compose中变量的状态
remember==缓存【甚至说局部单利】
remember的记住什么?主要是告诉当前组件,会记住某个值,或者说会缓存某个值,防止View重绘每次都用初始的值,如果已经记过了,就可能会用缓存的值,remember不是为了监听变化,相反,是为了提醒用缓存,变化是 MutableState的作用, 记住是remember的作用这样在重绘的时候,可以用新的值,MutableState会触发特定位置的重绘,remember会让重绘使用缓存值:
Remember the value produced by calculation. calculation will only be evaluated during the composition. Recomposition will always return the value produced by composition.
@Composable
inline fun <T> remember(
vararg keys: Any?,
crossinline calculation: @DisallowComposableCalls () -> T //用来计算值用的说通俗一些,一般来说key不变,只会调用一次,重绘之后,调用
): T {
var invalid = false
for (key in keys) invalid = invalid or currentComposer.changed(key)
return currentComposer.cache(invalid, calculation)
}
remember的lambda只是定义了一个如何去计算state值的算式,并没有执行,当这个函数组合且Compose框架判断需要依据lambda去获取这个state的值时,这时,这个lambda就会被执行,lambda的返回值就是计算结果,那么这个Composable函数后面访问到这个状态,访问的都是lambda的计算结果,
可变参数可以没有,也可以传递其他么rember的变量
@Composable
fun Greeting(name: String, age: Int) {
val message = remember(name, age) { "Hello $name, you are $age years old!" }
}
name跟age任何一个变了,都会重新计算。 mutableStateOf(0)是让compose感知到变回,以便重组,但是不负责记忆,remember会负责记忆。
@Composable
fun Component() {
val myText = mutableStateOf(0)
Column {
Text(text = myText.value.toString())
Button(onClick = { myText.value++ }) {}
}
}
会重组,但是不会变化,mutableStateOf其实相当于监听,观察者模式mutable State。但是没记住,每次重新调用Component都会初始。为了让让状态能够跨越重组而持久存在,就要把它放在函数外,其实类似于我们说的全局变量,不过有时候可能是自己触发自己,不需要放外边,那就自己remember,正如上面说的MutableStateFlow并不具备记忆能力,只具备触发的能力,变化之后,与之关联的UI要刷新,MutableStateFlow出现在那个Compose函数中,就会触发哪个函数重绘 用例:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val model = GreetingViewModel()
setContent {
val value = model._stateFlow.collectAsState()
//Greeting 状态提升 _stateFlow是MutableState ,并且被remember
Greeting(value.value, model.onEvent)
} }}
@Composable
fun Greeting(model: GreetingViewModel.UiState, event: (() -> Unit)?) {
// 这里只会计算一次 mutableStateOf(1) 否则直接返回缓存值
val count = remember { mutableStateOf(1) }
// 另外,如果是外部使用 count本身也是个mutable变量 就在局部自己负责自己,
LazyColumn(
) {
for (i in 0..100) {
item {
Button(onClick = {
event?.invoke()
}) { Text(
text = "" + model.title + " " + model.content + count.value,
modifier = Modifier.fillMaxSize() ) } } }}}
@Preview
@Composable
fun GreetingPreview() {
// 这里value是一个MutableState,同时它的值在这里被记录了,
// 并且记录的作用域应该也是可以调整的。毕竟model也牵扯到复用
Greeting(GreetingViewModel.UiState("title", "content"), null)
}
class GreetingViewModel : ViewModel() {
private val stateFlow = MutableStateFlow(UiState("title", "content")) //内部
val _stateFlow: StateFlow<UiState> = stateFlow.asStateFlow()//外部 更新限制在内部,其实也挺烦
val onEvent = fun() {
// 值更新了,同步更新缓存,同时会触发UI重绘MutableState ,这个对象被外边remember了
stateFlow.update {
it.copy(
content = "" + System.currentTimeMillis()
) } }
data class UiState(var title: String, var content: String)
}
上面的 Greeting如果不全局刷新,就不会计算从缓存再次去除值给count,但是count本事也是个mutable变量,而且在LazyColumn之外,它的变化,会引起LazyColumn 内部Item更新 ,并且值对于item而言是外部变量,所以也会更新。跟定一个外部mutableStateOf一样
val outerMutable = mutableStateOf(1)
@Composable
fun Greeting(model: GreetingViewModel.UiState, event: (() -> Unit)?) {
// 这里只会计算一次 mutableStateOf(1) 否则直接返回缓存值
val count = remember { mutableStateOf(1) }
// 另外,如果是外部使用 count本身也是个mutable变量 就在局部自己负责自己
LazyColumn(
、、、
Button(onClick = {
outerMutable.value++
}) {
Text(
text = "" + model.title + " " + model.content + count.value + outerMutable.value,
modifier = Modifier.fillMaxSize()
)
注意区分观察者模式与缓存的区别,也就是mutable与remmeber的区别
@Composable 到底是什么
Composable是函数,函数,kotlin函数,所以函数的一切特性还是存在的,参数,返回值等,它只是等被调用的函数,用来Compose。
触发 Compose 重绘的因素
触发原因 | 说明 | 示例 |
---|---|---|
可组合函数参数变化 | 任何 @Composable 函数的参数变化都会触发重组 |
MyComposable(text) 传入的新值不同 |
remember 变量变化 |
变量由 remember 或 mutableStateOf 维护,值变化会触发重组 |
val count by remember { mutableStateOf(0) } |
State 变化 |
mutableStateOf 变量改变,会触发依赖它的 Composable 重新执行 |
count++ 会导致依赖 count 的 UI 重新绘制 |
rememberUpdatedState 变化 |
rememberUpdatedState 用于在 LaunchedEffect 等中监听最新值,但不强制重组 |
rememberUpdatedState(text) 只更新值,不触发 UI 重绘 |
Composition 结构变化 | if/else 控制的 UI 结构发生改变 |
if (isVisible) Text("显示") else Text("隐藏") |
LaunchedEffect 重新执行 |
依赖值变化会重新执行 LaunchedEffect |
LaunchedEffect(count) { ... } |
derivedStateOf 变化 |
监听多个 State 变化,触发合并后的 UI 变化 |
val total by derivedStateOf { count1 + count2 } |
snapshotFlow
将state转换为Flow进行监听。LaunchedEffect 会多次触发,而 snapshotFlow 仅会触发一次(跳过重复值):
snapshotFlow { sliderValue }
.debounce(300) // 只在用户停顿后再发送
.collect { newValue -> updateVolume(newValue) }
使用 snapshotFlow 的最佳场景:
- 监听 State,但不想触发 UI 重新组合。
- 防抖 & 限流(如搜索输入框、滑动条)。
- 监听 State 并执行异步任务(如网络请求)。
重绘就是函数重新调用
Flow的解释
- asStateFlow() 适合 UI 状态: UI中配合collectAsState使用
持有最新数据,订阅时立即获取最新值。 适用于 ViewModel 存储 UI 状态(如 text、count)。
- 🔹 asSharedFlow() 适合事件通知:配合collect使用,不用考虑state更新UI,
不会存储数据,只推送新事件(如 Toast、Snackbar)。 适用于一次性事件,防止旧事件误触发。
-
callbackFlow 将回调转换为 Flow。
callbackFlow { val listener = object : MyListener { override fun onEvent(data: String) { trySend(data).onFailure { Log.e("callbackFlow", "数据丢失: $data") } } } MyApi.registerListener(listener) awaitClose { MyApi.unregisterListener(listener) } }.buffer(Channel.CONFLATED) // 仅保留最新数据
callbackFlow vs suspendCancellableCoroutine
callbackFlow vs suspendCancellableCoroutine 的区别
- callbackFlow:用于 将持续回调(如监听器)转换为 Flow,适用于多次回调的场景。
- suspendCancellableCoroutine:用于 将一次性回调转换为挂起函数,适用于单次回调的场景。
🔹 何时使用?
- ✅ 使用 callbackFlow
适用于:持续回调(监听 GPS、WebSocket、网络状态)。 示例:监听音量变化、监听传感器数据。
- ✅ 使用 suspendCancellableCoroutine
适用于:一次性回调(获取一次位置、请求一次权限)。 示例:获取用户当前位置、执行一次 API 调用。
MutableSharedFlow MutableStateFlow
MutableStateFlow用于状态的同步,无论是先注册,还是后注册,collet回调一定会有,适用于状态保持一致,而MutableSharedFlow主要用于事件传递通知,而且,非常重要的一点:先 emit() 才会有数据。
可以认为MutableSharedFlow一定要主动触发,才有collect回调,而MutableStateFlow第一次必定有回调,为了保持同步。所以如果是要监听事件,就用MutableSharedFlow,如果UI状态一致,用MutableStateFlow
参考文档
LiveData vs MutableStateFlow in Android Kotlin: A Comprehensive Comparison