Kotlin的Flow用法(实例加长加倍版)

一、Kotlin的简单概念

Kotlin Flow 是 Kotlin 协程库(kotlinx.coroutines)中的一个核心组件,用于处理异步数据流。它类似于 RxJava 的 Observable 或 Java 的 Stream,但更紧密集成于 Kotlin 协程,强调冷流(cold flow)的惰性执行和结构化并发。Flow 的设计目标是简化异步序列数据的处理,如网络请求、数据库查询、传感器数据或UI事件流。

  • 部分核心要点提前了解:
    1、Flow与协程 :Flow基于协程,收集collect()需要在协程中进行,而emit只能在协程或挂起函数中调用
    2、避免runBlocking :runBlocking会阻塞线程,仅适用测试或调试,本文为测试方便,使用了它。推荐使用 lifecycleScope、viewModelScope 或自定义 CoroutineScope 替代。
    3、生命周期集成 :在Android中,使用 lifecycleScope 和 repeatOnLifecycle 绑定 Flow 收集到页面生命周期,避免内存泄漏。
    4、热流与冷流 :StateFlow和SharedFlow都是热流。普通Flow冷流通过 shareIn 或 stateIn 转换为热流(如 SharedFlow 或 StateFlow),以实现数据共享或状态管理。
    5、与框架集成 :Flow 可无缝结合 Retrofit 和 Room,支持响应式数据处理。请看本文章后续的网络获取用户名(实例二)

二、如何创建Flow

以下是创建 Flow 的几种常见方法:

kotlin 复制代码
import kotlinx.coroutines.delay 
import kotlinx.coroutines.flow.*
//1、简单的flow创建
val simpleFlow = flow {
    emit(1)
    emit(2)
    emit(3)
}
//2、集合创建
val flowList = listOf(1, 2, 3).asFlow()

//3、序列创建
val flowSequence = sequenceOf(1, 2, 3).asFlow()

//4、使用flowOf
val flowOf = flowOf(1, 2, 3)

//5、动态创建
val flow = flow {
    for (i in 1..5) {
        delay(1000) // 模拟异步延迟
        emit(i * 2) // 发出值
    }
}

三、高级Flow操作符

  1. 转换操作符
kotlin 复制代码
Kotlin
class AdvancedFlowExamples {

    // 1. map操作符 - 数据转换
    fun mapExample(): Flow<String> = flowOf(1, 2, 3)
        .map { number ->
            "Number: $number"
        }

    // 2. transform操作符 - 更灵活的转换
    fun transformExample(): Flow<String> = flowOf(1, 2, 3)
        .transform { number ->
            emit("Start: $number")
            emit("End: $number")
        }

    // 3. filter操作符 - 数据过滤
    fun filterExample(): Flow<Int> = flowOf(1, 2, 3, 4, 5)
        .filter { it % 2 == 0 }

    // 4. take操作符 - 限制数量
    fun takeExample(): Flow<Int> = flowOf(1, 2, 3, 4, 5)
        .take(3)

    // 5. zip操作符 - 合并两个流
    fun zipExample(): Flow<String> {
        val flow1 = flowOf("A", "B", "C")
        val flow2 = flowOf(1, 2, 3)
        return flow1.zip(flow2) { letter, number ->
            "$letter$number"
        }
    }

    // 6. combine操作符 - 最新数据组合
    fun combineExample(): Flow<String> {
        val flow1 = MutableStateFlow("A")
        val flow2 = MutableStateFlow(1)
        
        CoroutineScope(Dispatchers.Default).launch {
            delay(1000)
            flow1.value = "B" // 更新 flow1
            delay(1000)
            flow2.value = 2 // 更新 flow2
        }
        return flow1.combine(flow2) { letter, number ->
            "$letter$number"
        }
    }
    
    // 7.onEach - 执行副作用操作,不改变数据
    flowOf(1, 2, 3)
        .onEach { println("Processing: $it") }  // 打印但不修改流
        .collect { println("Received: $it") }   // 最终收集到的还是 1, 2, 3
    
}

四、收集和消费

Flow是冷流,只有使用collect进行收集时才会执行,这句话是重点。每次收集都是从头开始发送数据。
用法1:基础收集

kotlin 复制代码
CoroutineScope(Dispatchers.Main).launch { 
    flowOf(1, 2, 3, 4, 5).collect { 
        println(it) 
    } 
}

用法2 :限定收集(take / drop)

实际应用:从无限流中截取前n条消息

kotlin 复制代码
val flow = flow {
    var i = 0
    while (true) {
        emit(i++)
    }
}
runBlocking {
    flow.take(3).collect { println(it) } // 输出: 0 1 2
}

用法3 :数据聚合或计算(reduce / fold)

实际应用:计算购物车总价,这个例子个人觉得有点重要,因为我曾经干过收银软件,泪啊。

kotlin 复制代码
val flow = flow {
    for (i in 1..10) {
        emit(i)
    }
}
runBlocking {
     //数据总和计算
    val total = flow.reduce { acc, value ->
        acc + value
    }
    //fold 用以带初始值的计算
    val total2 = flow.fold(100) { acc, value ->
        acc + value
    }
}

用法4 :线程切换

实际应用:在IO线程处理数据,在主线程刷新UI

kotlin 复制代码
val ioFlow = flow {
    emit(1)
}.flowOn(Dispatchers.IO) // 指定上游上下文

runBlocking {
    ioFlow.collect { /* 主线程 */ }
}

五、热流和冷流

什么叫热流?什么叫冷流?小伙伴们先理解下这两个概念。热流相当于足球比赛直播,你只能接受订阅后发出的数据。之所以称为热流,是因为它与收集者之间是独立的。冷流只有在收集时才生成,收集结束时上游停止,互相依存。

基本概念:

  • 无论是否有收集者,都会发送数据,注意与冷流的区别。
  • 多个收集者可以同时收集同一个SharedFlow
  • SharedFlow 默认通过 replay 参数缓存数据(replay=0 表示不缓存),而 StateFlow 始终只保留最新的单个值。

冷流vs热流

特性 冷流 热流(StateFlow/SharedFlow)
数据发送 随collect开始、结束 独立于收集,可以持续发送
收集端管理 自动(随collect结束) 自动(使用repeatOnLifecycle跟生命周期绑定)
发送端管理 自动 自动(由作用域控制,如 viewModelScope 自动取消)
典型场景 按需获取收据(API数据、数据库获取) 全局通知、状态管理

用法1:SharedFlow 实际应用:事件总线,如全局通知(登录状态变化)。

kotlin 复制代码
val shared = MutableSharedFlow<String>()

runBlocking {
    launch { shared.collect { println("Collector1: $it") } }
    launch { shared.collect { println("Collector2: $it") } }
    shared.emit("Event") // 两个收集者都收到
}

用法2 :StateFlow

实际应用:记录ViewModel中的UI状态,如加载中、成功、错误。可以看做livedata的替代者

kotlin 复制代码
val state = MutableStateFlow("loading")
runBlocking {
    launch { state.collect { println(it) } } // 输出 loading, Success
    state.value = "Success" // 更新状态,所有收集者收到
}

runBlocking一般为测试使用,还是写个Viewmodel中的例子吧。其他地方使用的时候,可参考此处调整。

kotlin 复制代码
class MyViewModel : ViewModel() {
    private val _uiState = MutableStateFlow(UiState.Loading)
    val uiState = _uiState.asStateFlow()
    fun loadData() {
        viewModelScope.launch {
            // 加载数据
            _uiState.value = UiState.Success
        }
    }
}

在Activity中接受数据

kotlin 复制代码
lifecycleScope.launch { 
    //页面在前台才开始收集
    repeatOnLifecycle(Lifecycle.State.RESUMED) {
        mediaVM.uiState.collect() { state ->
            when(state) {
                is UiState.Sucess -> {
                    //更新UI
                }
            }
        }
    }
}

也可以尝试以下用法,更简洁方便,此处是扩展方法,本质是一样的。
mediaVM.uiState.onEach {
    when(state) {
        is UiState.Success -> {
            //更新UI
        }
    }
}.launchIn(lifecycleScope)

StateFlow和SharedFlow 的区别

特性 StateFlow SharedFlow
初始值 必须有 可有可无
重播 最新值 可设置
应用 状态管理 事件广播
去重 自动去重 不去重

SharedFlow 特别适用于需要发送一次性事件(如 Toast 消息、跳转事件等)的场景,与 StateFlow 的持续状态管理形成互补。 当然你也可以使用SharedFlow编写一个事件总线,具体实现可参照文末的实例三

六、错误处理和重试

用法1 :捕获错误

实际应用:网络请求(数据库操作)失败时显示错误

kotlin 复制代码
val flow2 = flow {
    emit(1)
    emit(2)
    emit(3)
    throw Exception("错误")
}

runBlocking { 
    flow2.catch {
        println(it)
    }.collect { e ->
        println(e)
    }
}
//输出1、2、3、错误

用法2:重试- retry/retryWhen 实际应用:网络请求失败后重试操作

kotlin 复制代码
fun retryExample() {
    var attempt = 0
    
    val flow = flow {
        attempt++
        println("尝试次数: $attempt")
        emit(1)
        emit(2)
        if (attempt < 3) {
            throw RuntimeException("模拟失败")
        }
        emit(3)
    }
    
    runBlocking {
        flow
            .retry(3) { e -> // 最多重试3次
                println("重试因为异常: ${e.message}")
                true // 返回true表示需要重试
            }
            .catch { e ->
                println("最终失败: ${e.message}")
            }
            .collect { value ->
                println("retry: $value")
            }
    }
    runBlocking {
        flow
            .retryWhen { cause, attempt ->
                if (attempt < 3 && cause is RuntimeException) {
                    delay(1000) // 延迟1秒后重试
                    true // 继续重试
                } else {
                    false // 不再重试
                }
            }
            .catch { e ->
                println("处理最终异常: ${e.message}")
            }
            .collect { value ->
                println("retryWhen: $value")
            }
    }
}

retryWhen和retry的主要区别在于:retryWhen更加灵活,除了设置重试次数外,还可以设置其他条件。

七、背压和生命周期集成

数据的上游生产速度大于下游消费速度。一般处理方案都是丢弃部分数据。不建议缓存全部数据,极端情况下可能会导致OOM。

  • 背压处理:使用 buffer/conflate/collectLatest。
    实际应用:conflate 丢弃中间值,只处理最新。collectLatest()新值到达时取消对旧值的处理,替换例子中的collect()即可,不再单独举例。
kotlin 复制代码
fun conflateExample() {
    val fastProducer = flow {
        (1..5)
            .forEach {
                emit(it);
                delay(100)
            }
    }
    runBlocking {
        fastProducer.conflate().collect {
            delay(300);
            println(it)
        }  // 可能输出 1 3 5 (输出结果不一定,丢弃中间数据)
    }
}
kotlin 复制代码
//buffer的应用,缓冲区,建议设置缓存区满策略
fun bufferExample() {
    val fastProducer = flow {
        (1..20)
            .forEach {
                emit(it);
                //生产速度100ms
                delay(100)
            }
    }
    runBlocking {
        //背压处理,缓存五条,超出后丢弃旧的数据,保留最新数据
        fastProducer.buffer(capacity = 5, onBufferOverflow = BufferOverflow.DROP_OLDEST).collect {
            //消费速度500ms,模拟生产速度大于消费速度的场景,数据积压场景
            delay(500);
            println(it)
        } 
    }
}
  • 与 Lifecycle 集成(Android):使用 repeatOnLifecycle 避免内存泄漏
    实际应用: 页面处于活跃状态才刷新数据更新UI
kotlin 复制代码
lifecycleScope.launch {
    // repeatOnLifecycle 函数确保代码块只在指定的生命周期状态下执行
    // Lifecycle.State.STARTED 表示组件处于 STARTED 或 RESUMED 状态
    // 这样可以避免在后台时执行不必要的操作,节省资源
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        // 开始收集 Flow 数据流
        // collect 是一个挂起函数,会持续监听数据变化
        viewModel.uiState.collect { 
            // 每当 uiState 发出新值时,这里的代码就会执行
            // 可以安全地更新 UI,因为此时组件处于活跃状态
            // 更新 UI
            updateUI(data)
        }
    }
    // 当组件进入 STOPPED 状态时,collect 会暂停
    // 当组件重新进入 STARTED 状态时,会从上次中断的地方继续执行
}

八、实时聊天(实例一)

数据模型定义。

kotlin 复制代码
data class ChatMessage(
    val id: String,
    val chatId: String,
    val text: String,
    val senderId: String,
    val timestamp: Long,
    val status: MessageStatus
)

enum class MessageStatus { SENDING, SENT, FAILED }

enum class UserStatus { ONLINE, OFFLINE }

interface ChatDao {
    fun observeMessages(chatId: String): Flow<List<ChatMessage>>
    suspend fun insertMessage(message: ChatMessage)
}

interface ApiService {
    suspend fun sendMessage(message: ChatMessage): Response
}

data class SendMessageResponse(val messageId: String)

interface SocketManager {
    fun observeUserStatus(userId: String): Flow<UserStatus>
}

主要代码实现:

kotlin 复制代码
import kotlinx.coroutines.* 
import kotlinx.coroutines.flow.* 
import java.util.UUID

/**
 * 聊天数据仓库类,负责处理聊天相关的数据逻辑
 * 包括消息的获取、发送和用户状态监听等功能
 */
class ChatRepository {
    
     /**
     * 观察指定聊天室的消息流
     * 
     * @param chatId 聊天室ID
     * @return 返回按时间戳排序的消息列表Flow
     */
    fun observeMessages(chatId: String): Flow<List<ChatMessage>> = 
        database.chatDao()// 获取聊天数据访问对象
            .observeMessages(chatId)// 观察数据库中的消息变化
            .mapLatest { messages -> // 使用mapLatest处理最新数据,如果有新数据到达会取消之前的处理
                messages.sortedBy { it.timestamp } // 按时间戳升序排序消息
            }
            .distinctUntilChanged()//去重,避免重复数据
            .flowOn(Dispatchers.Default)

     /**
     * 发送消息并监听发送状态
     * 
     * @param message 要发送的聊天消息对象
     * @return 返回消息状态变化的Flow
     */
    fun sendMessage(message: ChatMessage): Flow<MessageStatus> = flow {
        // 1. 先发送"发送中"状态
        emit(MessageStatus.SENDING)
        
        // 2. 尝试发送消息
        try {
            // 调用API服务发送消息
            val response = apiService.sendMessage(message)
            if (response.isSuccessful) {
                // 3. 发送成功后,保存到本地数据库
                database.chatDao().insertMessage(message.copy(
                    //优先使用服务器返回ID,否则使用本地构建的id
                    id = response.body()?.messageId ?: message.id,
                    status = MessageStatus.SENT //更新消息状态为已发送
                ))
                //发射消息发送成功状态
                emit(MessageStatus.SENT)
            } else {
                //发射失败状态
                emit(MessageStatus.FAILED)
            }
        } catch (e: Exception) {
             //发射失败状态
            emit(MessageStatus.FAILED)
        }
    }
        .retryWhen { cause, attempt -> //当flow出现异常时,是否重试
            //最多重试三次且仅在IOException进行重试
            if (attempt < 3 && cause is IOException) {
                //延迟重试,指数退避,根据重试次数来决定。
                delay(2_000 * attempt) 
                true //返回true表示需要重试
            } else {
                false //不需要重试
            }
        }
        .flowOn(Dispatchers.IO) //在IO调度器上执行网络和数据库操作

     /**
     * 监听用户在线状态
     * 
     * @param userId 用户ID
     * @return 返回用户状态变化的Flow
     */
    fun observeUserStatus(userId: String): Flow<UserStatus> = 
        socketManager.observeUserStatus(userId) // 通过Socket管理器观察用户状态
            .distinctUntilChanged()// 过滤掉重复的状态变化
            .debounce(2000) //防抖动处理,2秒内无新状态变化时发射最后的值
}

/**
 * 聊天界面的ViewModel类,负责处理UI逻辑和数据绑定
 * 
 * @param repository 聊天数据仓库实例
 * @param chatId 当前聊天室ID
 */
class ChatViewModel(
    private val repository: ChatRepository,
    private val chatId: String
) : ViewModel() {
    //消息列表的状态流,用于在UI中观察消息变化
    private val _messages = MutableStateFlow<List<ChatMessage>>(emptyList())
    // 暴露只读的状态流, 防止外部发送数据
    val messages: StateFlow<List<ChatMessage>> = _messages.asStateFlow()

    /**
     *
     * 启动消息观察协程,将数据库中的消息同步到UI状态
     */
    init {
        repository.observeMessages(chatId)
            .onEach { messages ->
                _messages.value = messages
            }
            .launchIn(viewModelScope)
    }

    /**
     * 发送消息的方法
     * 
     * @param text 消息文本内容
     */
    fun sendMessage(text: String) {
    // 构造聊天消息对象
        val message = ChatMessage(
            id = UUID.randomUUID().toString(), // 生成唯一消息I
            chatId = chatId,
            text = text,
            senderId = currentUserId,// 假设 currentUserId 为 ViewModel 中的属性或从外部传入
            timestamp = System.currentTimeMillis(),
            status = MessageStatus.SENDING  // 初始状态为发送中
        )
        //发送消息,并监听状态变化,收集状态
        repository.sendMessage(message)
            .onEach { status ->
                // 更新消息状态
                updateMessageStatus(message.id, status)
            }
            .launchIn(viewModelScope)
    }
}

九、网络查询用户(实例二)

下面是一个完整的查询用户的接口实例

  • 数据模型定义
kotlin 复制代码
/**
 * 用户数据类,用于表示系统中的用户信息
 * 
 * @property id 用户唯一标识符
 * @property name 用户姓名
 * @property email 用户邮箱地址
 */
data class User(
    val id: Int,
    val name: String,
    val email: String
)
/**
 * 用于封装网络请求的返回结果
 * 
 * @param T 泛型参数,表示响应中数据的具体类型
 * @property data 接口返回的具体数据内容
 * @property status 响应状态码
 * @property message 错误信息,可选
 */
data class ApiResponse<T>(
    val data: T,
    val status: String,
    val message: String? = null
)
  • Repository层实现
kotlin 复制代码
/**
 * 定义用户相关操作接口
 * 使用Flow作为返回类型,方便响应式数据流处理
 */
interface UserRepository {
    /**
     * 获取所有用户列表
     * 
     * @return 返回包含用户列表的Flow,支持实时更新
     */
    fun getUsers(): Flow<List<User>>
     /**
     * 根据ID获取用户
     * 
     * @param id 用户ID
     * @return 返回包含用户信息的Flow,用户不存在时返回null
     */
    fun getUserById(id: Int): Flow<User?>
     /**
     * 根据查询条件搜索用户
     * 
     * @param query 搜索关键字
     * @return 返回匹配搜索条件的用户列表Flow
     */
    fun searchUsers(query: String): Flow<List<User>>
}

/**
 * 用户数据仓库实现类,负责具体的用户数据获取
 * 结合网络API和本地数据库提供统一的数据访问接口
 * 
 * @param apiService 网络API服务接口
 * @param database 本地数据库访问对象
 */
class UserRepositoryImpl(
    private val apiService: UserApiService,
    private val database: UserDatabase
) : UserRepository {

    // 场景1:从网络获取数据并缓存到本地
     /**
     * 获取用户列表,实现先显示缓存后更新的策略
     * 
     * @return 返回用户列表Flow,首先发送缓存数据,然后再发送网络最新数据
     */
    override fun getUsers(): Flow<List<User>> = flow {
        // 1. 先从本地数据库获取缓存数据
        val cachedUsers = database.userDao().getAllUsers()
        if (cachedUsers.isNotEmpty()) {
            emit(cachedUsers) // 先发射缓存数据,提升用户体验
        }

        // 2. 从网络获取最新数据
        try {
            val response = apiService.getUsers()
            if (response.isSuccessful) {
                val users = response.body()?.data ?: emptyList()
                
                // 3. 将最新数据保存到本地数据库
                database.userDao().insertAll(users)
                
                // 4. 发送最新数据给前台观察者
                emit(users)
            }
        } catch (e: Exception) {
            // 5. 网络错误时,如果无缓存数据则抛出异常,否则使用缓存数据
            if (cachedUsers.isEmpty()) {
                throw e //无缓存数据抛出异常
            }
        }
    }
        .catch { e ->
            // 全局异常处理,捕获上游异常并发送空列表
            println("Error fetching users: ${e.message}")
            emit(emptyList())
        }
        .flowOn(Dispatchers.IO) // 指定在IO线程执行耗时操作

    // 场景2:使用Flow进行搜索优化(防抖)
    /**
     * 搜索用户功能,包含防抖和去重优化
     * 
     * @param query 搜索关键字
     * @return 返回搜索结果Flow,具有防抖和去重特性
     */
    override fun searchUsers(query: String): Flow<List<User>> = flow {
        if (query.isBlank()) {
            emit(emptyList())
            return@flow
        }
        // 调用API搜索用户
        val response = apiService.searchUsers(query)
        emit(response.data)//发送搜索结果
    }
        .debounce(300) // 防抖,300ms内只执行最后一次搜索请求
        .distinctUntilChanged() // 避免重复搜索请求,相同查询不再执行
        .flowOn(Dispatchers.IO) //在io线程执行网络请求

    // 场景3:实时数据更新
    /**
     * 根据用户ID获取用户信息
     * 
     * @param id 用户ID
     * @return 返回用户信息Flow,数据库变化时自动更新
     */
    override fun getUserById(id: Int): Flow<User?> = database.userDao()
        .observeUserById(id)  // 从数据库观察指定ID的用户,返回Flow<User?>
        .map { user ->
            // 可以在这里进行数据转换,将用户名首字母大写(示例转换)
            user?.copy(name = user.name.replaceFirstChar { it.uppercase() })
        }
        .onEach { user ->
            // 其他操作,比如更新UI状态
            println("User updated: ${user?.name}")
        }
}
  • ViewModel层实现
kotlin 复制代码
/**
 * 用户界面ViewModel类,负责处理用户界面的业务逻辑和状态管理
 * 
 * @param repository 用户数据仓库实例,用于获取用户数据
 */
class UserViewModel(
    private val repository: UserRepository
) : ViewModel() {

    // 状态管理,用于状态内部更新
    private val _uiState = MutableStateFlow(UsersUiState())
    //状态只读状态流,对外暴露给UI层观察刷新
    val uiState: StateFlow<UsersUiState> = _uiState.asStateFlow()

    // 搜索查询流,用于管理搜索内容
    private val searchQuery = MutableStateFlow("")

    init {
        //观察用户和搜索结果
        observeUsers()
        observeSearchResults()
    }

    /**
     * 观察用户数据变化
     * 实现加载状态管理、错误处理和数据更新
     */
    private fun observeUsers() {
        repository.getUsers() //从仓库中获取用户数据flow
            .onStart { 
               //Flow 开始设置加载状态
                _uiState.value = _uiState.value.copy(isLoading = true)
            }
            .onCompletion { 
             //flow 完成时取消加载状态
                _uiState.value = _uiState.value.copy(isLoading = false)
            }
            .catch { e ->
                //捕获异常
                _uiState.value = _uiState.value.copy(
                    error = e.message,
                    isLoading = false //取消加载状态
                )
            }
            .onEach { users ->
            // 处理每个发射的用户列表数据
                _uiState.value = _uiState.value.copy(
                    users = users, //更新用户列表
                    isLoading = false //取消加载状态
                )
            }
            .launchIn(viewModelScope) //在Viewmodel协程中启动收集
    }

    /**
     * 观察搜索结果变化
     * 实现搜索防抖、过滤和结果更新
     */
    private fun observeSearchResults() {
        searchQuery
            .debounce(300) 300ms防抖
            .filter { it.length >= 2 } //只处理搜索内容大于2的查询
            .flatMapLatest { query -> //关键用法:将每个查询转换为搜索结果Flow
                repository.searchUsers(query) // 根据查询获取搜索结果
            }
            .catch { e ->
                println("Search error: ${e.message}")
            }
            .onEach { searchResults ->
            // 处理每个发射的搜索结果, // 更新搜索结果
                _uiState.value = _uiState.value.copy(searchResults = searchResults)
            }
            .launchIn(viewModelScope) // 在ViewModel作用域中启动收集
    }

    fun onSearchQueryChanged(query: String) {
        searchQuery.value = query // 更新搜索查询状态
    }

    /**
     * 刷新用户数据
     * 与observeUsers类似,但使用刷新状态而不是加载状态
     */
    fun refreshUsers() {
        repository.getUsers()  // 从仓库获取用户数据Flow
            .onStart { 
            // Flow开始时设置刷新状态
                _uiState.value = _uiState.value.copy(isRefreshing = true)
            }
            .onCompletion { 
             // Flow完成时取消刷新状态
                _uiState.value = _uiState.value.copy(isRefreshing = false)
            }
            .catch { e ->
             // 捕获并处理刷新异常
                _uiState.value = _uiState.value.copy(
                    error = e.message, 
                    isRefreshing = false // 取消刷新状态
                )
            }
            .onEach { users ->
            // 处理每个发射的用户列表数据
                _uiState.value = _uiState.value.copy(
                    users = users, // 更新用户列表
                    isRefreshing = false // 取消刷新状态
                )
            }
            .launchIn(viewModelScope)
    }
}

/**
 * 用户界面状态数据类,用于管理用户相关界面的所有状态
 * 
 * @property users 用户列表数据
 * @property searchResults 搜索结果列表
 * @property isLoading 是否正在加载数据
 * @property isRefreshing 是否正在刷新数据
 * @property error 错误信息,无错误时为null
 */
data class UsersUiState(
    val users: List<User> = emptyList(),
    val searchResults: List<User> = emptyList(),
    val isLoading: Boolean = false,
    val isRefreshing: Boolean = false,
    val error: String? = null
)

十、全局事件总线

注:要是想运用到实际项目中,请完善并做好测试工作。

  • 定义消息类型
kotlin 复制代码
/**
 * 应用事件密封类
 */
sealed class AppEvent {
    /**
     * 用户登录事件
     * @param userId 登录用户的唯一标识符
     */
    data class UserLogin(val userId: String) : AppEvent()
    /**
     * 用户登出事件
     */
    object UserLogout : AppEvent()
    /**
     * 网络状态变化事件
     * @param isConnected 表示当前网络是否连接的状态
     */
    data class NetworkStatus(val isConnected: Boolean) : AppEvent()
}
  • 创建事件总线
kotlin 复制代码
/**
 * FlowEventBus 是一个基于 SharedFlow 实现的事件总线对象,用于在应用内发布和订阅事件。
 */
object FlowEventBus {
    // 配置 SharedFlow,replay=0 表示不缓存历史消息,订阅者只收到新消息
    private val _events = MutableSharedFlow<AppEvent>(replay = 0, extraBufferCapacity = 1,
        onBufferOverflow = BufferOverflow.DROP_OLDEST)
    val events = _events.asSharedFlow() // 暴露只读 SharedFlow,防止外部发送数据

    /**
     * 发布事件到事件总线
     * 
     * @param event 要发布的应用事件对象
     */
    suspend fun post(event: AppEvent) {
        _events.emit(event)
    }

    /**
     * 发布用户登录事件的静态方法,可在 Java 代码中直接调用
     * 该方法会在 IO 线程中异步发布事件
     * 
     * @param userId 用户唯一标识符
     */
    @JvmStatic
    fun postLoginEvent(userId: String) {
        CoroutineScope(Dispatchers.IO).launch {
            post(AppEvent.UserLogin(userId))
        }
    }
}
  • 在Activity、Fragment中接收数据。当然你也可以在Viewmodel中接收,这边不再举例
kotlin 复制代码
lifecycleScope.launch {
    FlowEventBus.events.collect() { event ->
        when(event) {
            is AppEvent.UserLogin -> {
               showToast(event.userId)
            }
            is AppEvent.NetworkStatus -> {
               showToast("网络变化)
            }
            is AppEvent.UserLogout -> {
               showToast("登出")
            }
        }
    }
}
  • 发送数据
kotlin 复制代码
FlowEventBus.postLoginEvent("user123")

总结

  • 创建:flowOf、asFlow(无需协程),flow {}、channelFlow(需协程)。
  • 操作:map、filter、reduce 等支持异步处理。
  • 收集:需要协程,Android 用 lifecycleScope/repeatOnLifecycle。
  • 冷流特性:每次收集从头开始,收集结束上游停止。
  • 转换:冷流可转为热流(shareIn/stateIn)
  • 场景:数据库查询、API 分页、UI 事件处理。
相关推荐
叫我阿柒啊3 天前
从Java全栈到Vue3实战:一次真实面试的深度复盘
java·spring boot·微服务·vue3·响应式编程·前后端分离·restful api
QING6186 天前
使用扩展函数为 AppCompatTextView 提供了多段文本点击区域设置功能
android·kotlin·app
iOS阿玮8 天前
苹果市场常见的处罚邮件,最低处罚基本上听劝稳过。
uni-app·app·apple
小喷友8 天前
第9章 鸿蒙微内核与系统架构
前端·app·harmonyos
无知的前端11 天前
一文精通- iOS隐私权限
ios·程序员·app
阿里云云原生11 天前
从体验到系统工程丨上手评测国内首款 AI 电商 App
app
iOS阿玮12 天前
苹果审核被拒4.8.0条款,快速过审通关指南。
uni-app·app·apple
iOS阿玮12 天前
江湖传闻谷歌比苹果严格多了,那么到底有多狠?
uni-app·app·apple
产品研究员12 天前
AI内容创作APP原型设计详解:案例拆解与高效搭建技巧
app·aigc