核心組件關系圖
[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 方法回調
- 通過
-
數據綁定
- 單向數據流:
-
依賴管理
- 使用 Hilt 實現依賴注入:
@HiltViewModel
注解 ViewModel@Inject
構造函數注入 Repository
- 使用 Hilt 實現依賴注入:
Todo 應用最佳實踐實現
1. 添加依賴 (app/build.gradle)
dependencies {// Composeimplementation '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"// Lifecycleimplementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2"implementation "androidx.lifecycle:lifecycle-runtime-compose:2.6.2"// Roomimplementation "androidx.room:room-runtime:2.6.0"implementation "androidx.room:room-ktx:2.6.0"kapt "androidx.room:room-compiler:2.6.0"// Hiltimplementation "com.google.dagger:hilt-android:2.48"kapt "com.google.dagger:hilt-compiler:2.48"
}
2. 數據模型
@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 實現
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 實現
@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 實現
@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)
@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>>@Insertsuspend fun insert(todo: Todo)@Updatesuspend fun update(todo: Todo)@Deletesuspend fun delete(todo: Todo)
}
最佳實踐要點
-
狀態管理
- 使用密封類管理 UI 狀態(Loading/Success/Error)
- ViewModel 暴露不可變
StateFlow
,Compose 通過collectAsState()
觀察 - UI 組件保持無狀態,通過參數接收數據
-
性能優化
- 使用
remember
緩存計算結果 - 為 LazyColumn 的
items
設置唯一 key - 使用
derivedStateOf
處理復雜狀態轉換
- 使用
-
架構分層
-
測試策略
- ViewModel 測試:使用
TestCoroutineDispatcher
- Compose UI 測試:使用
createComposeRule
- Repository 測試:Mock 數據源
- ViewModel 測試:使用
-
用戶交互優化
- 添加 Undo 刪除功能(使用 Snackbar)
- 實現本地搜索/過濾
- 添加拖拽排序支持
-
錯誤處理
- 在 Repository 捕獲數據源異常
- ViewModel 將異常轉換為用戶友好消息
- UI 層顯示錯誤狀態并提供重試選項
此實現遵循了 Android 官方架構指南,結合了 Compose 的聲明式特性和 MVVM 的響應式數據管理,提供了可測試、可維護的現代化 Android 應用架構。