Compose 本身是声明式 UI 框架 ,天然适配「响应式架构」,传统的 MVC/MVP 因「命令式思维」(手动更新 UI)已不再适配,主流架构是 MVVM(最贴合) ,进阶场景会结合「单向数据流(UDF)」「Clean Architecture(整洁架构)」设计,核心目标是让数据状态单向流转,避免 UI 与业务逻辑耦合。
一、先明确:Compose 为什么"抛弃"MVC/MVP?
| 架构 | 核心问题(适配 Compose 时) |
|---|---|
| MVC | Controller 直接操作 UI(Compose 中 UI 由状态驱动,无" findViewById + 更新"的操作入口),易导致状态混乱 |
| MVP | Presenter 持有 View 接口,通过接口回调更新 UI(Compose 中无"View 接口",回调会破坏声明式思想,增加模板代码) |
| MVVM | ViewModel 持有可观察状态,UI 仅订阅状态(完全贴合 Compose "状态驱动 UI"的核心,无手动更新逻辑) |
二、Compose 主流架构:MVVM + 单向数据流(UDF)
这是 Google 官方推荐的架构,核心是「状态上移、单向流转」,分为 4 层,职责清晰且适配 Compose 的重组特性:
1. 架构分层(从上到下)
swift
┌─────────────────────────┐
│ UI 层(Composable 函数) │ 仅订阅状态、触发事件,无业务逻辑
└─────────────┬───────────┘
│ 发送事件(如点击)/ 接收状态更新
┌─────────────▼───────────┐
│ ViewModel 层(状态持有) │ 处理UI事件、管理状态、调用仓库层,不持有UI引用
└─────────────┬───────────┘
│ 调用接口/本地数据
┌─────────────▼───────────┐
│ Repository 层(数据仓库)│ 统一管理数据来源(网络/本地DB/SharedPref),解耦数据逻辑
└─────────────┬───────────┘
│ 数据请求/存储
┌─────────────▼───────────┐
│ Data 层(数据源)│ 网络接口(Retrofit)、本地DB(Room)、SP等
└─────────────────────────┘
2. 核心原则:单向数据流(UDF)
数据只能沿「Data → Repository → ViewModel → UI」方向流转,反向仅通过「事件」触发,避免状态混乱:
UI 触发事件(如点击按钮)→ ViewModel 处理事件 → 调用 Repository 获取数据 → ViewModel 更新状态 → UI 自动重组
三、完整示例:MVVM + 单向数据流
以"登录页"为例,覆盖状态管理、事件处理、数据请求全流程:
Step 1:Data 层(网络请求)
kotlin
// 1. 数据模型
data class LoginRequest(val username: String, val password: String)
data class LoginResponse(val token: String, val userId: String)
// 2. 网络接口(Retrofit)
interface LoginApi {
@POST("login")
suspend fun login(@Body request: LoginRequest): LoginResponse
}
// 3. 本地数据源(示例:SharedPref 保存 Token)
class LoginLocalDataSource(context: Context) {
private val sp = context.getSharedPreferences("login", Context.MODE_PRIVATE)
fun saveToken(token: String) {
sp.edit().putString("token", token).apply()
}
fun getToken(): String? = sp.getString("token", null)
}
Step 2:Repository 层(数据仓库)
统一管理数据来源,ViewModel 无需关心数据从网络/本地来:
kotlin
class LoginRepository(
private val api: LoginApi,
private val localDataSource: LoginLocalDataSource
) {
// 登录请求(挂起函数,适配协程)
suspend fun login(username: String, password: String): LoginResponse {
val response = api.login(LoginRequest(username, password))
// 登录成功后保存 Token 到本地
localDataSource.saveToken(response.token)
return response
}
}
Step 3:ViewModel 层(状态持有 + 事件处理)
核心是用 StateFlow/LiveData 持有可观察状态,Compose 订阅后自动重组:
kotlin
class LoginViewModel(
private val repository: LoginRepository
) : ViewModel() {
// 1. UI 状态(聚合所有需要驱动 UI 的数据)
private val _uiState = MutableStateFlow(LoginUiState())
val uiState: StateFlow<LoginUiState> = _uiState.asStateFlow() // 对外暴露只读流
// 2. 处理登录事件(UI 点击后调用)
fun handleLogin(username: String, password: String) {
// 标记加载中
_uiState.update { it.copy(isLoading = true, errorMsg = "") }
// 协程处理异步请求(viewModelScope 自动绑定生命周期)
viewModelScope.launch {
try {
val response = repository.login(username, password)
// 登录成功:更新状态
_uiState.update {
it.copy(
isLoading = false,
isLoginSuccess = true,
token = response.token
)
}
} catch (e: Exception) {
// 登录失败:更新错误状态
_uiState.update {
it.copy(
isLoading = false,
errorMsg = e.message ?: "登录失败,请重试"
)
}
}
}
}
// 定义 UI 状态数据类(聚合所有 UI 所需状态)
data class LoginUiState(
val isLoading: Boolean = false,
val errorMsg: String = "",
val isLoginSuccess: Boolean = false,
val token: String = ""
)
}
Step 4:UI 层(Composable 函数)
仅订阅 ViewModel 状态、触发事件,无任何业务逻辑:
ini
@Composable
fun LoginScreen(
viewModel: LoginViewModel = viewModel(), // 自动注入 ViewModel
onLoginSuccess: (String) -> Unit // 登录成功后的跳转回调
) {
// 1. 收集 ViewModel 状态(collectAsState 自动订阅,状态变化触发重组)
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
// 2. 输入框状态(UI 级临时状态,无需放 ViewModel)
var username by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
// 用户名输入框
OutlinedTextField(
value = username,
onValueChange = { username = it },
label = { Text("用户名") },
singleLine = true,
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
// 密码输入框
OutlinedTextField(
value = password,
onValueChange = { password = it },
label = { Text("密码") },
singleLine = true,
visualTransformation = PasswordVisualTransformation(),
modifier = Modifier.fillMaxWidth()
)
// 错误提示
if (uiState.errorMsg.isNotBlank()) {
Text(
text = uiState.errorMsg,
color = Color.Red,
modifier = Modifier.padding(top = 8.dp)
)
}
Spacer(modifier = Modifier.height(16.dp))
// 登录按钮(加载中禁用)
Button(
onClick = { viewModel.handleLogin(username, password) },
enabled = !uiState.isLoading,
modifier = Modifier.fillMaxWidth()
) {
if (uiState.isLoading) {
CircularProgressIndicator(
modifier = Modifier.size(20.dp),
strokeWidth = 2.dp
)
} else {
Text("登录")
}
}
}
// 登录成功:触发跳转(副作用,用 LaunchedEffect 避免重组重复执行)
LaunchedEffect(uiState.isLoginSuccess) {
if (uiState.isLoginSuccess) {
onLoginSuccess(uiState.token)
}
}
}
Step 5:Activity 层(仅承载 UI)
kotlin
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyAppTheme {
LoginScreen(onLoginSuccess = { token ->
// 登录成功跳转到首页
navController.navigate("home")
})
}
}
}
}
四、状态设计的核心规则(避坑关键)
1. 状态分类:区分"UI 临时状态"和"业务状态"
| 状态类型 | 示例 | 存放位置 | 原因 |
|---|---|---|---|
| UI 临时状态 | 输入框内容、列表滚动位置、弹窗显隐 | Composable 内(remember) | 仅影响单个 UI 组件,无需共享/持久化 |
| 业务状态 | 登录状态、用户信息、接口返回数据 | ViewModel(StateFlow) | 需跨组件共享、持久化,或随生命周期保活 |
2. 状态上移:避免局部状态耦合
- 若多个子组件需要共享状态(如"登录按钮"和"错误提示"共享
isLoading),将状态上移到父组件/ViewModel; - 示例:输入框内容是"局部状态",但"登录是否成功"是"全局业务状态",需放在 ViewModel。
3. 不可变状态:用 data class + copy 更新
- 所有状态数据类(如
LoginUiState)设计为不可变(val),更新时通过copy生成新对象; - 原因:Compose 依赖"状态对象引用变化"触发重组,可变对象会导致重组失效。
4. 生命周期绑定:用 collectAsStateWithLifecycle
- 收集 StateFlow 时,优先用
collectAsStateWithLifecycle()(而非collectAsState()),自动在 Activity/Fragment 进入后台时暂停收集,避免内存泄漏。
五、进阶:MVVM + Clean Architecture(整洁架构)
中大型项目可在 MVVM 基础上引入 Clean Architecture,进一步分层解耦:
markdown
┌─────────────────┐
│ Presentation 层 │ UI + ViewModel(与 MVVM 一致)
└─────────┬───────┘
│
┌─────────▼───────┐
│ Domain 层 │ 用例(UseCase)+ 领域模型(Entity),封装核心业务逻辑
│ │ 示例:LoginUseCase 封装"校验参数 + 调用仓库 + 处理结果"
└─────────┬───────┘
│
┌─────────▼───────┐
│ Data 层 │ Repository + 数据源(网络/本地),Domain 层不关心数据来源
└─────────────────┘
核心优势:Domain 层完全独立于 UI 和数据层,业务逻辑可单独测试,且易适配多端(如 Compose Multiplatform 复用 Domain 层)。
六、总结
1.主流架构:Compose 首选「MVVM + 单向数据流」,是官方推荐、适配性最好的方案;
2.状态流转:UI 触发事件 → ViewModel 处理 → Repository 获取数据 → ViewModel 更新状态 → UI 重组;
3.核心规则:状态分类存放、不可变设计、生命周期绑定、状态上移;
4.进阶优化:中大型项目加 Clean Architecture 的 Domain 层,解耦业务逻辑。