SharedFlow 中理解1、专门用于一次性事件(专用于事件,不是状态);2、多订阅者共享(一对多)

4 阅读3分钟

一、专门用于一次性事件(专用于事件,不是状态);

完整示例:点击按钮 → 发送事件 → 页面收到显示 Toast

完全符合你要的:Toast、提示、一次性操作


1. 先看 ViewModel(核心:SharedFlow 发送事件)
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.launch

class MyViewModel : ViewModel() {

    // 1. 定义:只在内部发消息
    private val _eventFlow = MutableSharedFlow<String>()

    // 2. 暴露:给外部页面只读接收
    val eventFlow: SharedFlow<String> = _eventFlow

    // 3. 点击按钮触发的方法
    fun onButtonClick() {
        viewModelScope.launch {
            // 发送一条 Toast 消息
            _eventFlow.emit("操作成功!这是一条一次性 Toast")
        }
    }
}
2. 再看页面(Compose)接收事件,弹 Toast
import android.widget.Toast
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.lifecycle.viewmodel.composeviewModel
import kotlinx.coroutines.flow.collect

@Composable
fun EventScreen(
    viewModel: MyViewModel = viewModel()
) {
    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {

        // -----------------------
        // 关键:监听一次性事件
        // -----------------------
        LaunchedEffect(Unit) {
            viewModel.eventFlow.collect { message ->
                // 收到事件 → 弹出 Toast(只会执行一次!)
                Toast.makeText(
                    this@EventScreen.context,
                    message,
                    Toast.LENGTH_SHORT
                ).show()
            }
        }

        // 点击按钮发送事件
        Button(onClick = { viewModel.onButtonClick() }) {
            Text("点我触发一次性 Toast")
        }
    }
}
3. 这个例子到底干了啥?(超直白解释)
  1. ViewModel 里用 SharedFlow 发事件

只发,不保存,页面退到后台再回来不会重复收到

  1. 页面用 collect 接收

收到就弹 Toast,执行一次就消失

  1. 完美符合:一次性操作

✅ 弹窗

✅ Toast

✅ 页面跳转

✅ 提示信息

4. 为什么必须用 SharedFlow?(重点)

如果用 StateFlow:

❌ 旋转屏幕会再次收到事件

❌ 回到页面会重复触发 Toast

SharedFlow:

✅ 事件发完就没

✅ 不会重复消费

✅ 真正一次性

5. 最简单记忆法
  • 显示内容(文字 / 列表 / 状态) → StateFlow
  • 弹出提示 / 点击事件 / 跳转 → SharedFlow

二、多订阅者共享(一对多)

核心比喻(先记住)

SharedFlow = 广播一个电台说话 → 所有收音机同时听到

场景说明

我们做一个:

  1. ViewModel 里发送一条消息
  2. 页面 A 收到
  3. 页面 B 收到
  4. 页面 C 也收到

真正的 一对多、多订阅者共享

1. ViewModel(只有一个,发送消息)
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.launch

class SharedViewModel : ViewModel() {

    // 共享数据流 —— 一对多核心
    private val _sharedFlow = MutableSharedFlow<String>()
    val sharedFlow: SharedFlow<String> = _sharedFlow

    // 发送消息:所有订阅者都会收到!
    fun sendMessage(msg: String) {
        viewModelScope.launch {
            _sharedFlow.emit(msg)
        }
    }
}
2. 主页面(发送消息 + 3 个订阅者同时接收)
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.composeViewModel
import kotlinx.coroutines.flow.collect

@Composable
fun SharedFlowDemoScreen(
    viewModel: SharedViewModel = viewModel()
) {
    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {

        // -----------------------------
        // 订阅者 1
        // -----------------------------
        var msg1 by remember { mutableStateOf("等待消息...") }
        LaunchedEffect(Unit) {
            viewModel.sharedFlow.collect { msg ->
                msg1 = "订阅者1收到:$msg"
            }
        }
        Text(msg1)

        Spacer(Modifier.height(10.dp))

        // -----------------------------
        // 订阅者 2
        // -----------------------------
        var msg2 by remember { mutableStateOf("等待消息...") }
        LaunchedEffect(Unit) {
            viewModel.sharedFlow.collect { msg ->
                msg2 = "订阅者2收到:$msg"
            }
        }
        Text(msg2)

        Spacer(Modifier.height(10.dp))

        // -----------------------------
        // 订阅者 3
        // -----------------------------
        var msg3 by remember { mutableStateOf("等待消息...") }
        LaunchedEffect(Unit) {
            viewModel.sharedFlow.collect { msg ->
                msg3 = "订阅者3收到:$msg"
            }
        }
        Text(msg3)

        Spacer(Modifier.height(30.dp))

        // -----------------------------
        // 按钮:发送一条消息
        // -----------------------------
        Button(onClick = {
            viewModel.sendMessage("大家好!我是共享消息")
        }) {
            Text("点我 → 发送广播消息")
        }
    }
}
运行效果(你一点击按钮就会看到)
订阅者1收到:大家好!我是共享消息
订阅者2收到:大家好!我是共享消息
订阅者3收到:大家好!我是共享消息

用最通俗的话讲清楚特性

1. 一对多是什么?

  • 一个发送者
  • 多个接收者
  • 同时收到同一条消息

就像:老师在教室说话 → 全班同学都听到。


2. SharedFlow 为什么能一对多?

因为它是 热流 + 广播模式

  • 不管有没有人订阅,它都可以发送
  • 发送一次,所有订阅者都能收到
  • 不绑定任何一个页面

3. 普通 Flow 做不到!

普通 Flow 是 一对一

  • 你发给 A,B 收不到
  • 发给 B,A 收不到
  • 必须一个一个发

SharedFlow 是 一对多

  • 一次发送
  • 全部收到

最经典使用场景(真实开发)

  • 登录成功后,多个页面同步刷新
  • 主题切换,所有页面同步变颜色
  • 发一条通知,所有页面同时响应
  • 全局事件总线(EventBus)

超精简总结

  • SharedFlow = 广播
  • 一个发,全部收
  • 一对多、多订阅者共享
  • 一次性事件、全局通知 首选!

如果你愿意,我还能给你做一个跨页面共享(A 页面发消息,B、C、D 页面同时收到)


注意:viewModel() 和 composeViewModel() 的区别

一句话总结:

现在的新版 Android 开发(Compose),你只需要记住:统一用 viewModel() 就够了!

composeViewModel() 是老版本的旧名字,现在已经合并成同一个函数了。