跳转到正文
YGJoon 技术博客

Android Kotlin Flow 数据流与状态管理

发布于:
·1.1k 字 · 约 3 分钟·作者:
Flow 在 Android UI 层的标准用法:StateFlow 持有界面状态、SharedFlow 发一次性事件,配冷热流区别和背压节流,一套组合拳。
Android Kotlin Flow 数据流与状态管理文章封面
目录

冷流与热流

类型代表实现特点
冷流 Coldflow {}、asFlow()每次订阅重新执行,惰性、无粘性
热流 HotStateFlow、SharedFlow持续存在,订阅者共享数据
val coldFlow = flow {
    emit(api.fetch())
}

val hotState = MutableStateFlow(HomeUiState.Loading)

Flow 基础操作

val result = flowOf(1, 2, 3)
    .map { it * 2 }
    .filter { it > 2 }
    .onEach { Timber.d("value=$it") }
    .toList()

StateFlow 与 UI 状态

sealed interface HomeUiState {
    data object Loading : HomeUiState
    data class Success(val data: List<Article>) : HomeUiState
    data class Error(val error: Throwable) : HomeUiState
}

class HomeViewModel @Inject constructor(
    private val repository: HomeRepository
) : ViewModel() {

    private val _uiState = MutableStateFlow<HomeUiState>(HomeUiState.Loading)
    val uiState: StateFlow<HomeUiState> = _uiState.asStateFlow()

    fun refresh() = viewModelScope.launch {
        repository.observeArticles()
            .map<HomeResult, HomeUiState> { HomeUiState.Success(it.list) }
            .catch { emit(HomeUiState.Error(it)) }
            .collect { _uiState.value = it }
    }
}

SharedFlow 与事件总线

class EventBus {
    private val _events = MutableSharedFlow<AppEvent>(extraBufferCapacity = 64)
    val events: SharedFlow<AppEvent> = _events.asSharedFlow()

    suspend fun emit(event: AppEvent) {
        _events.emit(event)
    }
}

viewModelScope.launch {
    eventBus.events.filterIsInstance<AppEvent.Toast>().collect { event ->
        _toastState.value = event.message
    }
}

背压策略与节流

searchQuery
    .debounce(300)
    .distinctUntilChanged()
    .flatMapLatest { keyword -> repository.search(keyword) }
    .conflate() // 仅保留最新值,丢弃旧值
    .collect { result -> render(result) }

Flow 与 Compose

@Composable
fun HomeScreen(viewModel: HomeViewModel = hiltViewModel()) {
    val state by viewModel.uiState.collectAsStateWithLifecycle()
    when (state) {
        is HomeUiState.Loading -> LoadingView()
        is HomeUiState.Success -> ArticleList(state.data)
        is HomeUiState.Error -> ErrorView(state.error)
    }
}

多数据源合并

val uiState = combine(
    userRepository.observeUser(),
    configRepository.observeConfig()
) { user, config ->
    UiState(user = user, config = config)
}.stateIn(
    scope = viewModelScope,
    started = SharingStarted.WhileSubscribed(5000),
    initialValue = UiState()
)

测试 Flow

@Test
fun `emit states`() = runTest {
    val flow = repository.observeArticles()
    val collectJob = launch(UnconfinedTestDispatcher()) {
        flow.test {
            assertEquals(HomeUiState.Loading, awaitItem())
            cancelAndIgnoreRemainingEvents()
        }
    }
    collectJob.join()
}

常见问题

问题原因解决方案
shareIn 内存泄漏scope 生命周期过长使用 viewModelScope 或手动取消
collect 阻塞主线程在主线程执行了耗时操作添加 flowOn(Dispatchers.IO) 或 withContext
重复请求未使用 distinctUntilChanged比较数据差异,或在 Repository 层缓存

实战案例:StateFlow 就是”有记忆的 Stream”

Dart 里没有 StateFlow,但有个很像的东西:BehaviorSubject(rxdart 包)。两者都解决同一个问题——“晚订阅的人也要拿到最新值”。

我在 Flutter 电商项目(GetX 技术栈)里,购物车数量存在 GetX 的 RxInt 里,任何页面 Obx(() => ...) 都能读到最新值。Kotlin 的 StateFlow 是官方版的同一个东西:_uiState = MutableStateFlow(UiState.Loading),UI 层 collectAsState()。区别在于:

  1. StateFlow 要求初始值,BehaviorSubject 可以没有(用 BehaviorSubject() 空构造)。这个设计逼你在 ViewModel 里想清楚”第一帧显示什么”——Loading 还是空数据,Kotlin 替你做了决定。
  2. StateFlow 是协程原生的,stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), initial) 一行搞定”有人看才上游开工、没人看 5 秒后停工”。Dart 里要自己管 Stream 的 listen/cancel,GetX 的 ever/debounce 算是补了这块。

Google 的架构指南现在把”单一数据源 + StateFlow 向下流”作为标准答案,Dart 侧最接近的是 Riverpod 的 StateNotifier。跨平台看多了会发现:大家最后都收敛到同一个模式,只是名字不同。

参考:Android 官方文档《A safer way to collect flows》;Kotlin Flow 官方文档

总结

  1. Flow 提供统一的响应式数据流模型,结合 StateFlow/SharedFlow 可覆盖大部分 UI 状态与事件需求。
  2. 借助 combine、flatMapLatest 等操作符,能够灵活拼装多数据源。
  3. 管理好生命周期、背压策略与测试,是 Flow 在实际项目中稳定运行的关键。

下一篇将探讨 Kotlin 在 Jetpack Compose 中的应用与最佳实战。


分享这篇文章:


相关文章

上一篇
Android Kotlin 协程基础与调度器
下一篇
Android Kotlin DataStore 配置与迁移