核心组件关系图
css
[View] -- 观察 --> [ViewModel] -- 操作 --> [Repository]
| |
Compose UI StateFlow/LiveData
| |
用户交互事件 Room/Retrofit
| |
[ViewModel] <-- 数据更新 -- [Data Sources]
开发流程详解
-
数据层 (Model)
- 实体类定义:Room 实体或数据类
- Repository 模式:
- 统一管理数据源(Room/Retrofit/文件)
- 提供干净的 API 给 ViewModel
-
ViewModel 层
- 使用
androidx.lifecycle
组件 - 核心职责:
- 持有 UI 状态(
StateFlow
/MutableStateFlow
) - 处理业务逻辑
- 暴露不可变状态给 UI 层
- 持有 UI 状态(
- 使用协程管理异步操作
- 使用
-
UI 层 (Compose)
- 基于状态的声明式编程
- 关键组件:
@Composable
函数构建 UIremember
管理组件状态ViewModel
通过viewModel()
获取
- 状态管理:
- 通过
collectAsState()
观察 ViewModel 状态 - 用户事件通过 ViewModel 方法回调
- 通过
-
数据绑定
-
单向数据流:
graph LR A[用户操作] --> B[调用 ViewModel 方法] B --> C[更新 Model 层] C --> D[ViewModel 更新 State] D --> E[Compose 自动重组]
-
-
依赖管理
- 使用 Hilt 实现依赖注入:
@HiltViewModel
注解 ViewModel@Inject
构造函数注入 Repository
- 使用 Hilt 实现依赖注入:
Todo 应用最佳实践实现
1. 添加依赖 (app/build.gradle)
gradle
dependencies {
// Compose
implementation 'androidx.activity:activity-compose:1.8.0'
implementation "androidx.compose.material3:material3:1.1.2"
implementation "androidx.compose.ui:ui-tooling-preview:1.5.4"
debugImplementation "androidx.compose.ui:ui-tooling:1.5.4"
// Lifecycle
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2"
implementation "androidx.lifecycle:lifecycle-runtime-compose:2.6.2"
// Room
implementation "androidx.room:room-runtime:2.6.0"
implementation "androidx.room:room-ktx:2.6.0"
kapt "androidx.room:room-compiler:2.6.0"
// Hilt
implementation "com.google.dagger:hilt-android:2.48"
kapt "com.google.dagger:hilt-compiler:2.48"
}
2. 数据模型
kotlin
@Entity(tableName = "todos")
data class Todo(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val title: String,
val description: String = "",
val isCompleted: Boolean = false,
val createdAt: LocalDateTime = LocalDateTime.now()
)
3. Repository 实现
kotlin
interface TodoRepository {
fun getAllTodos(): Flow<List<Todo>>
suspend fun addTodo(todo: Todo)
suspend fun updateTodo(todo: Todo)
suspend fun deleteTodo(todo: Todo)
}
class TodoRepositoryImpl @Inject constructor(
private val todoDao: TodoDao
) : TodoRepository {
override fun getAllTodos(): Flow<List<Todo>> =
todoDao.getAll().map { list -> list.sortedByDescending { it.createdAt } }
override suspend fun addTodo(todo: Todo) =
todoDao.insert(todo)
override suspend fun updateTodo(todo: Todo) =
todoDao.update(todo)
override suspend fun deleteTodo(todo: Todo) =
todoDao.delete(todo)
}
4. ViewModel 实现
kotlin
@HiltViewModel
class TodoViewModel @Inject constructor(
private val repository: TodoRepository
) : ViewModel() {
// UI 状态封装
data class TodoUiState(
val todos: List<Todo> = emptyList(),
val isLoading: Boolean = false,
val error: String? = null
)
// 使用 MutableStateFlow 管理状态
private val _uiState = MutableStateFlow(TodoUiState(isLoading = true))
val uiState: StateFlow<TodoUiState> = _uiState.asStateFlow()
init {
loadTodos()
}
private fun loadTodos() {
viewModelScope.launch {
repository.getAllTodos()
.catch { e ->
_uiState.value = TodoUiState(error = e.message)
}
.collect { todos ->
_uiState.value = TodoUiState(todos = todos)
}
}
}
fun addTodo(title: String, description: String = "") {
viewModelScope.launch {
val newTodo = Todo(
title = title,
description = description
)
repository.addTodo(newTodo)
}
}
fun toggleTodoCompletion(todo: Todo) {
viewModelScope.launch {
repository.updateTodo(todo.copy(isCompleted = !todo.isCompleted))
}
}
fun deleteTodo(todo: Todo) {
viewModelScope.launch {
repository.deleteTodo(todo)
}
}
}
5. Compose UI 实现
kotlin
@Composable
fun TodoScreen(
viewModel: TodoViewModel = viewModel()
) {
val uiState by viewModel.uiState.collectAsState()
var showDialog by remember { mutableStateOf(false) }
var newTodoTitle by remember { mutableStateOf("") }
Scaffold(
topBar = { TopAppBar(title = { Text("Todo List") }) },
floatingActionButton = {
FloatingActionButton(onClick = { showDialog = true }) {
Icon(Icons.Filled.Add, "Add Todo")
}
}
) { padding ->
when {
uiState.isLoading -> Center { CircularProgressIndicator() }
uiState.error != null -> Center { Text("Error: ${uiState.error}") }
else -> TodoList(
todos = uiState.todos,
onToggleComplete = viewModel::toggleTodoCompletion,
onDelete = viewModel::deleteTodo,
modifier = Modifier.padding(padding)
)
}
}
if (showDialog) {
AlertDialog(
onDismissRequest = { showDialog = false },
title = { Text("Add New Todo") },
text = {
TextField(
value = newTodoTitle,
onValueChange = { newTodoTitle = it },
label = { Text("Title") }
)
},
confirmButton = {
Button(
onClick = {
viewModel.addTodo(newTodoTitle)
newTodoTitle = ""
showDialog = false
},
enabled = newTodoTitle.isNotBlank()
) {
Text("Add")
}
},
dismissButton = {
Button(onClick = { showDialog = false }) {
Text("Cancel")
}
}
)
}
}
@Composable
fun TodoList(
todos: List<Todo>,
onToggleComplete: (Todo) -> Unit,
onDelete: (Todo) -> Unit,
modifier: Modifier = Modifier
) {
LazyColumn(modifier = modifier) {
items(
items = todos,
key = { it.id }
) { todo ->
TodoItem(
todo = todo,
onCheckedChange = { onToggleComplete(todo) },
onDelete = { onDelete(todo) }
)
}
}
}
@Composable
fun TodoItem(
todo: Todo,
onCheckedChange: (Boolean) -> Unit,
onDelete: () -> Unit
) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
) {
Row(
modifier = Modifier.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Checkbox(
checked = todo.isCompleted,
onCheckedChange = onCheckedChange
)
Column(
modifier = Modifier.weight(1f)
) {
Text(
text = todo.title,
style = MaterialTheme.typography.titleMedium,
textDecoration = if (todo.isCompleted) TextDecoration.LineThrough else null
)
if (todo.description.isNotBlank()) {
Text(
text = todo.description,
style = MaterialTheme.typography.bodySmall
)
}
}
IconButton(onClick = onDelete) {
Icon(Icons.Filled.Delete, "Delete")
}
}
}
}
6. 数据库配置 (Room)
kotlin
@Database(entities = [Todo::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun todoDao(): TodoDao
}
@Dao
interface TodoDao {
@Query("SELECT * FROM todos")
fun getAll(): Flow<List<Todo>>
@Insert
suspend fun insert(todo: Todo)
@Update
suspend fun update(todo: Todo)
@Delete
suspend fun delete(todo: Todo)
}
最佳实践要点
-
状态管理
- 使用密封类管理 UI 状态(Loading/Success/Error)
- ViewModel 暴露不可变
StateFlow
,Compose 通过collectAsState()
观察 - UI 组件保持无状态,通过参数接收数据
-
性能优化
- 使用
remember
缓存计算结果 - 为 LazyColumn 的
items
设置唯一 key - 使用
derivedStateOf
处理复杂状态转换
- 使用
-
架构分层
graph TD A[UI Layer] -->|调用| B[ViewModel] B -->|调用| C[Repository] C -->|本地数据| D[Room] C -->|远程数据| E[Retrofit] -
测试策略
- ViewModel 测试:使用
TestCoroutineDispatcher
- Compose UI 测试:使用
createComposeRule
- Repository 测试:Mock 数据源
- ViewModel 测试:使用
-
用户交互优化
- 添加 Undo 删除功能(使用 Snackbar)
- 实现本地搜索/过滤
- 添加拖拽排序支持
-
错误处理
- 在 Repository 捕获数据源异常
- ViewModel 将异常转换为用户友好消息
- UI 层显示错误状态并提供重试选项
此实现遵循了 Android 官方架构指南,结合了 Compose 的声明式特性和 MVVM 的响应式数据管理,提供了可测试、可维护的现代化 Android 应用架构。