LiveData 为什么不够好 - 迁移到 Flow 完全指南
从 LiveData 迁移到 StateFlow/SharedFlow 的完整实践
目录
- LiveData 的局限性
- Flow 核心概念
- 迁移策略
- ViewModel 中的迁移
- Compose 中的使用
- 常见问题解决
1. LiveData 的局限性
LiveData 的问题
| 问题 |
描述 |
| 线程切换不可控 |
postValue 会切换到主线程,但逻辑不透明 |
| 缺少取消机制 |
无法取消正在发射的数据 |
| 不是 Flow |
无法与 Kotlin 协程生态集成 |
| 背压不支持 |
数据量大会导致问题 |
| 错误处理弱 |
没有 try-catch 机制 |
LiveData 行为示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class MyViewModel : ViewModel() { private val _data = MutableLiveData<Data>() val data: LiveData<Data> = _data fun loadData() { repository.getData().observeForever { result -> _data.postValue(result) } } }
|
2. Flow 核心概念
StateFlow vs SharedFlow
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| ┌─────────────────────────────────────────────────────────────────────┐ │ Flow 类型对比 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ StateFlow SharedFlow │ │ ───────── ────────── │ │ • 有初始值 • 无初始值 │ │ • 始终有当前值 • 只有订阅者时才会发射 │ │ • 适合 UI 状态 • 适合事件/一次性消息 │ │ • replay = 1 • replay 可配置 │ │ │ │ 示例: 示例: │ │ val uiState = StateFlow(...) val events = SharedFlow(...) │ │ │ │ ┌────────────────┐ ┌────────────────┐ │ │ │ UI State │ │ One-time │ │ │ │ 始终保持最新 │ │ Events │ │ │ └────────────────┘ └────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘
|
创建 Flow
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| private val _uiState = MutableStateFlow(UiState()) val uiState: StateFlow<UiState> = _uiState.asStateFlow()
private val _events = MutableSharedFlow<Event>() val events: SharedFlow<Event> = _events.asSharedFlow()
val events = MutableSharedFlow<Event>( replay = 1 )
val userFlow: Flow<User> = flow { emit(loadFromCache()) emit(loadFromNetwork()) }
|
3. 迁移策略
Step 1: 将 LiveData 替换为 StateFlow
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| class OldViewModel : ViewModel() { private val _user = MutableLiveData<User>() val user: LiveData<User> = _user fun loadUser(userId: String) { viewModelScope.launch { try { val result = api.getUser(userId) _user.value = result } catch (e: Exception) { _user.value = null } } } }
class NewViewModel : ViewModel() { private val _user = MutableStateFlow<User?>(null) val user: StateFlow<User?> = _user.asStateFlow() fun loadUser(userId: String) { viewModelScope.launch { try { val result = api.getUser(userId) _user.value = result } catch (e: Exception) { _user.value = null } } } }
|
Step 2: 处理 loading 状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| class OldViewModel : ViewModel() { val isLoading = MutableLiveData<Boolean>() val user = MutableLiveData<User>() val error = MutableLiveData<Error?>() }
data class UiState( val isLoading: Boolean = false, val user: User? = null, val error: Error? = null )
class NewViewModel : ViewModel() { private val _uiState = MutableStateFlow(UiState()) val uiState: StateFlow<UiState> = _uiState.asStateFlow() fun loadUser(userId: String) { viewModelScope.launch { _uiState.value = _uiState.value.copy(isLoading = true) try { val user = api.getUser(userId) _uiState.value = _uiState.value.copy( isLoading = false, user = user ) } catch (e: Exception) { _uiState.value = _uiState.value.copy( isLoading = false, error = e ) } } } }
|
Step 3: 处理事件 (一次性消息)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
sealed class UiEvent { data class ShowSnackbar(val message: String) : UiEvent() data class Navigate(val route: String) : UiEvent() }
class EventViewModel : ViewModel() { private val _events = MutableSharedFlow<UiEvent>() val events: SharedFlow<UiEvent> = _events.asSharedFlow() fun onUserClick(userId: String) { viewModelScope.launch { _events.emit(UiEvent.Navigate("/user/$userId")) } } }
lifecycleScope.launch { viewModel.events.collect { event -> when (event) { is UiEvent.ShowSnackbar -> showSnackbar(event.message) is UiEvent.Navigate -> navigate(event.route) } } }
|
4. ViewModel 中的迁移
完整示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| class UserViewModel( private val repository: UserRepository ) : ViewModel() { private val _uiState = MutableStateFlow(UserUiState()) val uiState: StateFlow<UserUiState> = _uiState.asStateFlow() private val _events = MutableSharedFlow<UserEvent>() val events: SharedFlow<UserEvent> = _events.asSharedFlow() fun loadUser(userId: String) { viewModelScope.launch { _uiState.value = _uiState.value.copy(isLoading = true) try { val user = repository.getUser(userId) _uiState.value = _uiState.value.copy( isLoading = false, user = user ) } catch (e: Exception) { _uiState.value = _uiState.value.copy( isLoading = false, error = e.message ) _events.emit(UserEvent.ShowError(e.message ?: "Unknown error")) } } } fun refresh() { _uiState.value.user?.id?.let { loadUser(it) } } }
data class UserUiState( val isLoading: Boolean = false, val user: User? = null, val error: String? = null )
sealed class UserEvent { data class ShowError(val message: String) : UserEvent() object NavigateBack : UserEvent() }
|
5. Compose 中的使用
collectAsState vs collectAsStateWithLifecycle
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @Composable fun UserScreen(viewModel: UserViewModel = hiltViewModel()) { val uiState by viewModel.uiState.collectAsState() val uiState by viewModel.uiState.collectAsStateWithLifecycle() val uiState by viewModel.uiState.collectAsStateWithLifecycle( initialValue = UserUiState() ) when { uiState.isLoading -> LoadingView() uiState.error != null -> ErrorView(uiState.error) uiState.user != null -> UserView(uiState.user) } }
|
依赖
1 2 3 4 5 6 7 8 9 10 11
| dependencies { implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.7.0" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0" implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0" implementation platform('androidx.compose:compose-bom:2024.02.00') implementation 'androidx.compose.runtime:runtime-livedata' }
|
使用 LaunchedEffect 收集事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @Composable fun UserScreen(viewModel: UserViewModel) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() val snackbarHostState = remember { SnackbarHostState() } LaunchedEffect(Unit) { viewModel.events.collect { event -> when (event) { is UserEvent.ShowError -> { snackbarHostState.showSnackbar(event.message) } is UserEvent.NavigateBack -> { } } } } Scaffold(snackbarHost = { SnackbarHost(snackbarHostState) }) { } }
|
6. 常见问题解决
Q1: 首次发射初始值导致 UI 闪烁
1 2 3 4 5 6 7
| val uiState by viewModel.uiState.collectAsStateWithLifecycle()
val uiState by viewModel.uiState.collectAsStateWithLifecycle( initialValue = UserUiState() )
|
Q2: 协程取消问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| class BadViewModel : ViewModel() { private val _data = MutableStateFlow("") val data: StateFlow<String> = _data.asStateFlow() init { viewModelScope.launch { while (true) { _data.value = produceData() delay(1000) } } } }
class GoodViewModel : ViewModel() { private val _data = MutableStateFlow("") val data: StateFlow<String> = _data.asStateFlow() init { viewModelScope.launch { repeatOnLifecycle(State) { while (true) { _data.value = produceData() delay(1000) } } } } }
|
Q3: 多个数据源合并
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| class ProfileViewModel( private val userRepository: UserRepository, private val settingsRepository: SettingsRepository ) : ViewModel() { private val userFlow = userRepository.getUser() private val settingsFlow = settingsRepository.getSettings() val profileState: StateFlow<ProfileState> = combine( userFlow, settingsFlow ) { user, settings -> ProfileState( name = user.name, avatar = user.avatar, theme = settings.theme, notifications = settings.notifications ) }.stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), initialValue = ProfileState() ) }
|
迁移检查清单
| 检查项 |
状态 |
| LiveData → StateFlow |
✅ |
| loading/error 状态合并到 UI State |
✅ |
| 一次性事件使用 SharedFlow |
✅ |
| 使用 collectAsStateWithLifecycle |
✅ |
| 使用 repeatOnLifecycle 收集 |
✅ |
| 移除 observe() / observeForever() |
✅ |
总结
1 2 3 4 5 6 7
| LiveData 缺点 Flow 优势 ──────────── ────────── 线程切换不透明 协程控制,线程安全 无取消机制 协程自动取消 无背压支持 背压策略可选 错误处理困难 try-catch 天然支持 非 Kotlin 一等公民 与 Kotlin 生态完美集成
|
相关文章: