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 应用架构。

相关推荐
游戏开发爱好者83 分钟前
iOS WebView 远程调试实战 解决表单输入被键盘遮挡和焦点丢失问题
android·ios·小程序·https·uni-app·iphone·webview
没有用的阿吉22 分钟前
adb 指令大全
android·adb调试
amy_jork33 分钟前
android studio打包vue
android·vue.js·android studio
编程乐学38 分钟前
网络资源模板--基于Android Studio 实现的校园心里咨询预约App
android·android studio·预约系统·大作业·移动端开发·安卓移动开发·心理咨询预约
涵涵子RUSH42 分钟前
android studio(NewsApiDemo)100%kotlin
android·kotlin·android studio
九鼎创展科技1 小时前
直播一体机技术方案解析:基于RK3588S的硬件架构特性
android·嵌入式硬件·硬件架构
峥嵘life2 小时前
Android14 锁屏密码修改为至少6位
android·安全
2501_9159184110 小时前
iOS WebView 调试实战 localStorage 与 sessionStorage 同步问题全流程排查
android·ios·小程序·https·uni-app·iphone·webview
Digitally11 小时前
如何永久删除安卓设备中的照片(已验证)
android·gitee
hmywillstronger12 小时前
【Settlement】P1:整理GH中的矩形GRID角点到EXCEL中
android·excel