前言
ViewModel一般都是用于数据的处理,Compose中渲染数据,处理消息提示或路由跳转。
但是有时我们想在ViewModel中触发某个逻辑后触发页面消息,或者路由跳转,该怎样实现呢?
- 一种是在vm中创建状态,页面中监听状态,只不过这样的写法不太优雅。
- 另一种是vm中发送事件,页面中监听事件。
下面就以全局消息提示来演示用法:
全局消息提示
在 Jetpack Compose 中,通常不建议直接在 ViewModel 中处理 UI 相关操作(如弹出消息提示),因为这违反了单一职责原则和关注点分离。
ViewModel 应该只负责管理数据和业务逻辑,而 UI 相关的操作应该由 Composable 组件处理。
正确的做法是:
- 在 ViewModel 中定义一个状态来表示是否需要显示消息
- 在 Composable 中观察这个状态
- 当状态变化时,在 Composable 中显示消息提示
以下是一个实现示例:
事件类
首先,创建一个密封类来表示 UI 事件:
1 | sealed class UiEvent { |
VM发送事件
然后,在 ViewModel 中使用 Channel 或 StateFlow 来发送事件:
1 | import androidx.lifecycle.ViewModel |
页面事件接收
在 Composable 中观察事件并显示消息:
1 | import androidx.compose.foundation.background |
注意
其中使用Popup,是为了防止消息被其他的Popup遮挡。
组件引用
直接使用
1 | MsgReceiveComp(loginViewModel) |
这种方式遵循了 Jetpack 的最佳实践,保持了 ViewModel 和 UI 层的分离,同时能够从 ViewModel 触发消息提示。
如果你需要使用 Toast 而不是 Snackbar,方法类似,只需在 Composable 中收到事件时调用 Toast.makeText() 即可。
全局使用
上面说的是组件内用,但是每个组件都这样整,优点繁琐,并且位置也不一定是整个屏幕,所以这里配置全局使用。
在App的组件中添加接收组件
1 | import androidx.compose.foundation.layout.Box |
全局变量中添加
1 | object CommonData { |
使用的时候这样调用
1 | CommonData.appViewModel?.showSnackbarAction("未选择教材") |