Kotlin 数据流与单双向绑定

Kotlin 数据流与单双向绑定


1. 数据流基础概念

单向数据流 vs 双向数据流

复制代码
单向数据流:State → UI(数据驱动 UI,UI 不直接修改 State)

    ┌─────────────────────────────────────┐
    │            ViewModel                 │
    │  ┌─────────┐    ┌──────────────┐   │
    │  │ State   │───→│ UI 展示       │   │
    │  └─────────┘    └──────────────┘   │
    │       ↑                           │
    │       │ 用户操作                   │
    │       │                           │
    │  ┌────┴────┐    ┌──────────────┐   │
    │  │ Event   │───→│ 处理逻辑      │   │
    │  └─────────┘    └──────────────┘   │
    └─────────────────────────────────────┘

双向绑定:State ↔ UI(UI 变化自动更新 State,State 变化自动更新 UI)

    ┌─────────────────────────────────────┐
    │            ViewModel                 │
    │  ┌─────────┐    ┌──────────────┐   │
    │  │ State   │←──→│ UI 展示       │   │
    │  └─────────┘    └──────────────┘   │
    └─────────────────────────────────────┘

2. Kotlin 协程数据流

Flow(冷流)

kotlin 复制代码
// 创建 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(状态流,推荐)

kotlin 复制代码
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(事件流)

kotlin 复制代码
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(管道,协程间通信)

kotlin 复制代码
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 老牌状态容器)

kotlin 复制代码
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 转换

kotlin 复制代码
// 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 互转

kotlin 复制代码
// 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)

kotlin 复制代码
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 写法

kotlin 复制代码
// 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 中使用双向绑定

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 中定义双向绑定属性

kotlin 复制代码
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 中启用双向绑定

kotlin 复制代码
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(状态提升,单向数据流)

kotlin 复制代码
@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)

kotlin 复制代码
@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 / 密封类做状态和事件

kotlin 复制代码
// 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(状态变化驱动展示)← ← ← ← ← ← ← ← ← ←
kotlin 复制代码
// 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 专用,可观察数据,生命周期感知
相关推荐
逻辑驱动的ken2 小时前
Java高频面试场景题25
java·开发语言·深度学习·面试·职场和发展
小白学鸿蒙2 小时前
Unity 3D 2023解压安装,配置安卓运行环境后打包安卓应用(踩坑无数之差点放弃)
android·unity·游戏引擎
AI人工智能+电脑小能手3 小时前
【大白话说Java面试题】【Java基础篇】第32题:Java的异常处理机制是什么
java·开发语言·后端·面试
阿巴斯甜3 小时前
2026小知识点(9)
android
古月-一个C++方向的小白4 小时前
MySQL数据库——数据类型
android·数据库·mysql
無限進步D5 小时前
Java 面向对象高级 接口
java·开发语言
两年半的个人练习生^_^6 小时前
Java日志框架和使用、日志记录规范
java·开发语言·开发规范
张小潇6 小时前
AOSP15 WMS/AMS系统开发 - WindowManagerService finishDraw与prepareSurface流程详解
android
杨凯凡6 小时前
【032】排查入门:jstack、heap dump、Arthas 初识
java·开发语言·后端