Android Jetpack Compose + MVVM 开发流程深度分析

核心组件关系图

css 复制代码
[View] -- 观察 --> [ViewModel] -- 操作 --> [Repository]
  |                              |
Compose UI                    StateFlow/LiveData
  |                              |
用户交互事件                       Room/Retrofit
  |                              |
[ViewModel] <-- 数据更新 -- [Data Sources]

开发流程详解

  1. 数据层 (Model)

    • 实体类定义:Room 实体或数据类
    • Repository 模式:
      • 统一管理数据源(Room/Retrofit/文件)
      • 提供干净的 API 给 ViewModel
  2. ViewModel 层

    • 使用 androidx.lifecycle 组件
    • 核心职责:
      • 持有 UI 状态(StateFlow/MutableStateFlow
      • 处理业务逻辑
      • 暴露不可变状态给 UI 层
    • 使用协程管理异步操作
  3. UI 层 (Compose)

    • 基于状态的声明式编程
    • 关键组件:
      • @Composable 函数构建 UI
      • remember 管理组件状态
      • ViewModel 通过 viewModel() 获取
    • 状态管理:
      • 通过 collectAsState() 观察 ViewModel 状态
      • 用户事件通过 ViewModel 方法回调
  4. 数据绑定

    • 单向数据流:

      graph LR A[用户操作] --> B[调用 ViewModel 方法] B --> C[更新 Model 层] C --> D[ViewModel 更新 State] D --> E[Compose 自动重组]
  5. 依赖管理

    • 使用 Hilt 实现依赖注入:
      • @HiltViewModel 注解 ViewModel
      • @Inject 构造函数注入 Repository

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)
}

最佳实践要点

  1. 状态管理

    • 使用密封类管理 UI 状态(Loading/Success/Error)
    • ViewModel 暴露不可变 StateFlow,Compose 通过 collectAsState() 观察
    • UI 组件保持无状态,通过参数接收数据
  2. 性能优化

    • 使用 remember 缓存计算结果
    • 为 LazyColumn 的 items 设置唯一 key
    • 使用 derivedStateOf 处理复杂状态转换
  3. 架构分层

    graph TD A[UI Layer] -->|调用| B[ViewModel] B -->|调用| C[Repository] C -->|本地数据| D[Room] C -->|远程数据| E[Retrofit]
  4. 测试策略

    • ViewModel 测试:使用 TestCoroutineDispatcher
    • Compose UI 测试:使用 createComposeRule
    • Repository 测试:Mock 数据源
  5. 用户交互优化

    • 添加 Undo 删除功能(使用 Snackbar)
    • 实现本地搜索/过滤
    • 添加拖拽排序支持
  6. 错误处理

    • 在 Repository 捕获数据源异常
    • ViewModel 将异常转换为用户友好消息
    • UI 层显示错误状态并提供重试选项

此实现遵循了 Android 官方架构指南,结合了 Compose 的声明式特性和 MVVM 的响应式数据管理,提供了可测试、可维护的现代化 Android 应用架构。

相关推荐
阿巴斯甜12 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker12 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952713 小时前
Andorid Google 登录接入文档
android
黄林晴15 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_1 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android