Kotlin 数据流与单双向绑定
1. 数据流基础概念
单向数据流 vs 双向数据流
单向数据流:State → UI(数据驱动 UI,UI 不直接修改 State)
┌─────────────────────────────────────┐
│ ViewModel │
│ ┌─────────┐ ┌──────────────┐ │
│ │ State │───→│ UI 展示 │ │
│ └─────────┘ └──────────────┘ │
│ ↑ │
│ │ 用户操作 │
│ │ │
│ ┌────┴────┐ ┌──────────────┐ │
│ │ Event │───→│ 处理逻辑 │ │
│ └─────────┘ └──────────────┘ │
└─────────────────────────────────────┘
双向绑定:State ↔ UI(UI 变化自动更新 State,State 变化自动更新 UI)
┌─────────────────────────────────────┐
│ ViewModel │
│ ┌─────────┐ ┌──────────────┐ │
│ │ State │←──→│ UI 展示 │ │
│ └─────────┘ └──────────────┘ │
└─────────────────────────────────────┘
2. Kotlin 协程数据流
Flow(冷流)
// 创建 Flow
fun getUsers(): Flow<List<User>> = flow {
while (true) {
val users = api.getUsers() // 挂起获取数据
emit(users) // 发送数据
delay(5000) // 每 5 秒刷新
}
}.flowOn(Dispatchers.IO) // IO 线程执行
// 收集 Flow
lifecycleScope.launch {
getUsers().collect { users ->
adapter.submitList(users)
}
}
// flowOf 创建固定数据流
val numbers = flowOf(1, 2, 3, 4, 5)
val filtered = flowOf(1, 2, 3, 4, 5).filter { it > 2 }
// asFlow 扩展
listOf(1, 2, 3).asFlow()
"Hello".asFlow()
StateFlow(状态流,推荐)
class UserViewModel : ViewModel() {
// 私有可变状态
private val _uiState = MutableStateFlow<UiState>(UiState.Idle)
// 对外暴露只读状态
val uiState: StateFlow<UiState> = _uiState
// 简化写法(推荐)
val userName = MutableStateFlow("")
val userName2: StateFlow<String> = MutableStateFlow("")
// 使用
fun updateName(name: String) {
_uiState.value = UiState.Loading
// 更新状态
_uiState.value = UiState.Success(data)
}
}
// 收集
lifecycleScope.launch {
viewModel.uiState.collect { state ->
when (state) {
is UiState.Loading -> showLoading()
is UiState.Success -> showData(state.data)
is UiState.Error -> showError(state.message)
}
}
}
SharedFlow(事件流)
class EventViewModel : ViewModel() {
// 事件流,一次性消费
private val _events = MutableSharedFlow<Event>()
val events: SharedFlow<Event> = _events
// 带缓存的事件流
private val _messages = MutableSharedFlow<String>(
replay = 0, // 重播数量
extraBufferCapacity = 64 // 额外缓冲
)
// 发送事件
fun login() {
viewModelScope.launch {
try {
api.login()
_events.emit(Event.LoginSuccess)
} catch (e: Exception) {
_events.emit(Event.Error(e.message))
}
}
}
// 订阅事件
viewModel.events.collect { event ->
when (event) {
is Event.LoginSuccess -> navigateToHome()
is Event.Error -> showError(event.message)
}
}
}
Channel(管道,协程间通信)
class ViewModel : ViewModel() {
private val channel = Channel<Int>()
// 发送
fun sendValue(value: Int) {
viewModelScope.launch {
channel.send(value * 2)
}
}
// 接收(一次性的)
fun receiveValue() {
viewModelScope.launch {
val value = channel.receive()
_result.value = value
}
}
// 关闭
channel.close()
}
StateFlow vs SharedFlow vs Flow
| 类型 |
特点 |
场景 |
Flow |
冷流,按需发射 |
一次性数据请求 |
StateFlow |
热流,始终持有最新状态 |
UI 状态(必选) |
SharedFlow |
热流,事件流,可重播 |
一次性事件、Toast、导航 |
Channel |
一对一管道 |
协程间一对一通信 |
3. LiveData(Android 老牌状态容器)
class UserViewModel : ViewModel() {
// 可变 LiveData
private val _user = MutableLiveData<User>()
// 对外只读
val user: LiveData<User> = _user
// 带初始值
private val _name = MutableLiveData("默认值")
fun loadUser() {
_user.value = loadingState
// ...
_user.value = successState
_user.postValue(successState) // 后台线程安全
}
}
// Activity 观察
viewModel.user.observe(this) { user ->
textView.text = user.name
}
// Fragment 观察(自动随 Activity 生命周期)
viewModel.user.observe(viewLifecycleOwner) { user ->
// 只有 Fragment 可见时触发
}
LiveData 转换
// map
val name: LiveData<String> = userLiveData.map { it.name }
// switchMap
val userId = MutableLiveData<String>()
val user: LiveData<User> = userId.switchMap { id ->
repository.getUser(id)
}
// MediatorLiveData(合并多个数据源)
val result = MediatorLiveData<Result>()
result.addSource(source1) { value -> result.value = merge(value, result.value) }
result.addSource(source2) { value -> result.value = merge(result.value, value) }
4. LiveData + StateFlow 互转
// LiveData → StateFlow
val liveData: LiveData<T> = ...
val stateFlow: StateFlow<T> = liveData.asFlow().stateIn(scope, SharingStarted, initialValue)
// StateFlow → LiveData
val stateFlow: StateFlow<T> = ...
val liveData: LiveData<T> = stateFlow.asLiveData()
5. 单向数据绑定(UI State 驱动)
传统写法(findViewById)
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: UserViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProvider(this)[UserViewModel::class.java]
// 观察状态,单向驱动 UI
viewModel.uiState.observe(this) { state ->
when (state) {
is UiState.Loading -> showLoading()
is UiState.Success -> updateUI(state.data)
is UiState.Error -> showError(state.message)
}
}
}
// 用户操作 → 事件流向 ViewModel
fun onButtonClick(view: View) {
viewModel.onEvent(Event.Refresh)
}
}
ViewBinding 写法
// build.gradle.kts
android {
buildFeatures { viewBinding = true }
}
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
viewModel.uiState.observe(this) { state ->
when (state) {
is UiState.Loading -> {
binding.progressBar.visibility = View.VISIBLE
binding.recyclerView.visibility = View.GONE
}
is UiState.Success -> {
binding.progressBar.visibility = View.GONE
binding.recyclerView.visibility = View.VISIBLE
adapter.submitList(state.data)
}
is UiState.Error -> {
binding.progressBar.visibility = View.GONE
Snackbar.make(binding.root, state.message, Snackbar.LENGTH_LONG).show()
}
}
}
// 单向:用户操作 → ViewModel
binding.buttonRefresh.setOnClickListener {
viewModel.onEvent(Event.Refresh)
}
binding.buttonRetry.setOnClickListener {
viewModel.onEvent(Event.Retry)
}
}
}
6. 双向数据绑定(ViewModel ↔ UI)
XML 中使用双向绑定
<!-- activity_main.xml -->
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="viewModel"
type="com.example.UserViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- 单向:text 从 ViewModel 显示到 UI -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewModel.userName}" />
<!-- 双向:editText 同时更新 ViewModel,ViewModel 也更新 editText -->
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={viewModel.userName}" <!-- 注意 @= -->
android:hint="输入用户名" />
<!-- 双向绑定按钮 -->
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="提交"
android:onClick="@{() -> viewModel.submit()}" />
</LinearLayout>
</layout>
ViewModel 中定义双向绑定属性
class UserViewModel : ViewModel() {
// 双向绑定:用户输入 → ViewModel(单向:ViewModel → UI)
val userName = MutableStateFlow("")
val userEmail = MutableStateFlow("")
val userAge = MutableStateFlow("")
// 表单是否有效
val isFormValid: StateFlow<Boolean> = combine(
userName, userEmail, userAge
) { name, email, age ->
name.isNotBlank() && email.isValidEmail() && age.isNotBlank() && age.toIntOrNull() != null
}.stateIn(viewModelScope, SharingStarted, false)
// 提交
fun submit() {
viewModelScope.launch {
if (isFormValid.value) {
repository.saveUser(User(
name = userName.value,
email = userEmail.value,
age = userAge.value.toInt()
))
}
}
}
// 清除表单
fun clearForm() {
userName.value = ""
userEmail.value = ""
userAge.value = ""
}
}
// 邮箱校验扩展
fun String.isValidEmail(): Boolean =
Patterns.EMAIL_ADDRESS.matcher(this).matches()
Activity 中启用双向绑定
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val viewModel: UserViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 创建 binding
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
// 设置 lifecycleOwner(必须)
binding.lifecycleOwner = this
binding.viewModel = viewModel
// 收集 StateFlow 更新 UI
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.isFormValid.collect { valid ->
binding.buttonSubmit.isEnabled = valid
}
}
}
}
}
7. DataBinding vs ViewBinding vsfindViewById
| 方式 |
编译检查 |
性能 |
功能 |
findViewById |
❌ 无 |
最快 |
无 |
ViewBinding |
✅ 有 |
快 |
生成绑定类,无数据绑定 |
DataBinding |
✅ 有 |
中等 |
完整双向绑定,XML 中写逻辑 |
8. Compose 中的单向/双向数据流
State Hoisting(状态提升,单向数据流)
@Composable
fun UserScreen(viewModel: UserViewModel = viewModel()) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
when (val state = uiState) {
is UiState.Loading -> LoadingScreen()
is UiState.Success -> UserListScreen(users = state.data)
is UiState.Error -> ErrorScreen(message = state.message)
}
}
// 子组件接收数据(单向)
@Composable
fun UserListScreen(users: List<User>) {
LazyColumn {
items(users) { user ->
UserItem(user = user) // 只接收数据,不修改
}
}
}
// 事件回调向上传递
@Composable
fun UserItem(user: User, onUserClick: (User) -> Unit) {
Text(
text = user.name,
modifier = Modifier.clickable { onUserClick(user) } // 事件向上传
)
}
双向绑定(remember + mutableStateOf)
@Composable
fun LoginScreen() {
var username by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
// 双向绑定:用户输入 → 状态,状态变化 → UI
OutlinedTextField(
value = username,
onValueChange = { username = it }, // 双向绑定
label = { Text("用户名") }
)
OutlinedTextField(
value = password,
onValueChange = { password = it }, // 双向绑定
label = { Text("密码") }
)
Button(
onClick = { /* 提交 */ },
enabled = username.isNotBlank() && password.length >= 6
) {
Text("登录")
}
}
9. Sealed Class / 密封类做状态和事件
// UI 状态(单向数据流)
sealed class UiState<out T> {
data object Idle : UiState<Nothing>()
data object Loading : UiState<Nothing>()
data class Success<T>(val data: T) : UiState<T>()
data class Error(val message: String) : UiState<Nothing>()
}
// 用户意图(单向事件流)
sealed class UserIntent {
data object LoadUsers : UserIntent()
data class DeleteUser(val id: String) : UserIntent()
data class UpdateUser(val user: User) : UserIntent()
}
// Side Effect(一次性事件)
sealed class SideEffect {
data class ShowToast(val message: String) : SideEffect()
data object NavigateToHome : SideEffect()
data class ShowError(val message: String) : SideEffect()
}
// ViewModel 中使用
class UserViewModel : ViewModel() {
private val _uiState = MutableStateFlow<UiState<List<User>>>(UiState.Idle)
val uiState: StateFlow<UiState<List<User>>> = _uiState
private val _sideEffect = MutableSharedFlow<SideEffect>()
val sideEffect: SharedFlow<SideEffect> = _sideEffect
// 处理意图
fun processIntent(intent: UserIntent) {
when (intent) {
is UserIntent.LoadUsers -> loadUsers()
is UserIntent.DeleteUser -> deleteUser(intent.id)
is UserIntent.UpdateUser -> updateUser(intent.user)
}
}
private suspend fun emitSideEffect(effect: SideEffect) {
_sideEffect.emit(effect)
}
}
// UI 收集
lifecycleScope.launch {
viewModel.sideEffect.collect { effect ->
when (effect) {
is SideEffect.ShowToast -> showToast(effect.message)
is SideEffect.NavigateToHome -> navigateToHome()
is SideEffect.ShowError -> showErrorDialog(effect.message)
}
}
}
10. 完整 MVVM 单向数据流示例
数据流:
UI(用户操作)→ ViewModel(Intent)→ Repository → API/DB
↓
State 更新
↓
UI(状态变化驱动展示)← ← ← ← ← ← ← ← ← ←
// 1. State(UI 状态,单向数据流)
data class HomeUiState(
val isLoading: Boolean = false,
val users: List<User> = emptyList(),
val error: String? = null,
val selectedTab: Tab = Tab.ALL
)
// 2. Intent(用户操作)
sealed class HomeIntent {
data object LoadUsers : HomeIntent()
data class SelectTab(val tab: Tab) : HomeIntent()
data class DeleteUser(val id: String) : HomeIntent()
data object Refresh : HomeIntent()
}
// 3. ViewModel
class HomeViewModel(
private val repository: UserRepository = UserRepository()
) : ViewModel() {
private val _uiState = MutableStateFlow(HomeUiState())
val uiState: StateFlow<HomeUiState> = _uiState.asStateFlow()
fun processIntent(intent: HomeIntent) {
when (intent) {
is HomeIntent.LoadUsers -> loadUsers()
is HomeIntent.SelectTab -> selectTab(intent.tab)
is HomeIntent.DeleteUser -> deleteUser(intent.id)
is HomeIntent.Refresh -> refresh()
}
}
private fun loadUsers() {
viewModelScope.launch {
_uiState.update { it.copy(isLoading = true, error = null) }
repository.getUsers()
.onSuccess { users ->
_uiState.update { it.copy(isLoading = false, users = users) }
}
.onFailure { error ->
_uiState.update { it.copy(isLoading = false, error = error.message) }
}
}
}
private fun selectTab(tab: Tab) {
_uiState.update { it.copy(selectedTab = tab) }
loadUsers()
}
private fun deleteUser(id: String) {
viewModelScope.launch {
repository.deleteUser(id)
.onSuccess { loadUsers() }
.onFailure { error ->
_uiState.update { it.copy(error = error.message) }
}
}
}
private fun refresh() = loadUsers()
}
// 4. UI(Activity)
class HomeActivity : AppCompatActivity() {
private val viewModel: HomeViewModel by viewModels()
private lateinit var binding: ActivityHomeBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityHomeBinding.inflate(layoutInflater)
setContentView(binding.root)
// 收集状态,单向驱动 UI
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state ->
render(state)
}
}
}
// 用户操作,单向流入 ViewModel
binding.swipeRefresh.setOnRefreshListener {
viewModel.processIntent(HomeIntent.Refresh)
}
binding.tabs.addOnTabSelectedListener {
viewModel.processIntent(HomeIntent.SelectTab(Tab.valueOf(it.text.toString())))
}
}
private fun render(state: HomeUiState) {
binding.swipeRefresh.isRefreshing = state.isLoading
adapter.submitList(state.users)
state.error?.let {
Snackbar.make(binding.root, it, Snackbar.LENGTH_LONG).show()
}
}
}
核心原则
| 概念 |
原则 |
| 单向数据流 |
State → UI(状态驱动展示),Intent → ViewModel(事件驱动逻辑) |
| 双向绑定 |
ViewModel ↔ UI 自动同步,适合表单输入 |
| StateFlow |
热流,始终持有最新值,用于 UI 状态 |
| SharedFlow |
热流,事件流,一次性消费 |
| Sealed Class |
状态和事件用密封类,类型安全 |
| Intent |
用户操作封装为 Intent,ViewModel 统一处理 |
| SideEffect |
一次性事件(Toast/导航)用 SharedFlow |
核心区别:
StateFlow:持续状态,UI 始终展示最新值
SharedFlow:一次性事件,消费即消失
LiveData:Android 专用,可观察数据,生命周期感知