android-jetpack-compose-expert

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Android Jetpack Compose Expert

Android Jetpack Compose 专家指南

Overview

概述

A comprehensive guide for building production-quality Android applications using Jetpack Compose. This skill covers architectural patterns, state management with ViewModels, navigation type-safety, and performance optimization techniques.
这是一份使用Jetpack Compose构建生产级Android应用的综合指南。本指南涵盖架构模式、结合ViewModel的状态管理、类型安全导航以及性能优化技巧。

When to Use This Skill

适用场景

  • Use when starting a new Android project with Jetpack Compose.
  • Use when migrating legacy XML layouts to Compose.
  • Use when implementing complex UI state management and side effects.
  • Use when optimizing Compose performance (recomposition counts, stability).
  • Use when setting up Navigation with type safety.
  • 在使用Jetpack Compose启动新Android项目时使用。
  • 在将传统XML布局迁移到Compose时使用。
  • 在实现复杂UI状态管理和副作用处理时使用。
  • 在优化Compose性能(重组次数、稳定性)时使用。
  • 在设置类型安全导航时使用。

Step-by-Step Guide

分步指南

1. Project Setup & Dependencies

1. 项目设置与依赖

Ensure your
libs.versions.toml
includes the necessary Compose BOM and libraries.
kotlin
[versions]
composeBom = "2024.02.01"
activityCompose = "1.8.2"

[libraries]
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
确保你的
libs.versions.toml
包含必要的Compose BOM和库。
kotlin
[versions]
composeBom = "2024.02.01"
activityCompose = "1.8.2"

[libraries]
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }

2. State Management Pattern (MVI/MVVM)

2. 状态管理模式(MVI/MVVM)

Use
ViewModel
with
StateFlow
to expose UI state. Avoid exposing
MutableStateFlow
.
kotlin
// UI State Definition
data class UserUiState(
    val isLoading: Boolean = false,
    val user: User? = null,
    val error: String? = null
)

// ViewModel
class UserViewModel @Inject constructor(
    private val userRepository: UserRepository
) : ViewModel() {

    private val _uiState = MutableStateFlow(UserUiState())
    val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()

    fun loadUser() {
        viewModelScope.launch {
            _uiState.update { it.copy(isLoading = true) }
            try {
                val user = userRepository.getUser()
                _uiState.update { it.copy(user = user, isLoading = false) }
            } catch (e: Exception) {
                _uiState.update { it.copy(error = e.message, isLoading = false) }
            }
        }
    }
}
结合
StateFlow
使用
ViewModel
来暴露UI状态。避免暴露
MutableStateFlow
kotlin
// UI State Definition
data class UserUiState(
    val isLoading: Boolean = false,
    val user: User? = null,
    val error: String? = null
)

// ViewModel
class UserViewModel @Inject constructor(
    private val userRepository: UserRepository
) : ViewModel() {

    private val _uiState = MutableStateFlow(UserUiState())
    val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()

    fun loadUser() {
        viewModelScope.launch {
            _uiState.update { it.copy(isLoading = true) }
            try {
                val user = userRepository.getUser()
                _uiState.update { it.copy(user = user, isLoading = false) }
            } catch (e: Exception) {
                _uiState.update { it.copy(error = e.message, isLoading = false) }
            }
        }
    }
}

3. Creating the Screen Composable

3. 创建屏幕可组合项

Consume the state in a "Screen" composable and pass data down to stateless components.
kotlin
@Composable
fun UserScreen(
    viewModel: UserViewModel = hiltViewModel()
) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()

    UserContent(
        uiState = uiState,
        onRetry = viewModel::loadUser
    )
}

@Composable
fun UserContent(
    uiState: UserUiState,
    onRetry: () -> Unit
) {
    Scaffold { padding ->
        Box(modifier = Modifier.padding(padding)) {
            when {
                uiState.isLoading -> CircularProgressIndicator()
                uiState.error != null -> ErrorView(uiState.error, onRetry)
                uiState.user != null -> UserProfile(uiState.user)
            }
        }
    }
}
在「屏幕」可组合项中消费状态,并将数据向下传递给无状态组件。
kotlin
@Composable
fun UserScreen(
    viewModel: UserViewModel = hiltViewModel()
) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()

    UserContent(
        uiState = uiState,
        onRetry = viewModel::loadUser
    )
}

@Composable
fun UserContent(
    uiState: UserUiState,
    onRetry: () -> Unit
) {
    Scaffold { padding ->
        Box(modifier = Modifier.padding(padding)) {
            when {
                uiState.isLoading -> CircularProgressIndicator()
                uiState.error != null -> ErrorView(uiState.error, onRetry)
                uiState.user != null -> UserProfile(uiState.user)
            }
        }
    }
}

Examples

示例

Example 1: Type-Safe Navigation

示例1:类型安全导航

Using the new Navigation Compose Type Safety (available in recent versions).
kotlin
// Define Destinations
@Serializable
object Home

@Serializable
data class Profile(val userId: String)

// Setup NavHost
@Composable
fun AppNavHost(navController: NavHostController) {
    NavHost(navController, startDestination = Home) {
        composable<Home> {
            HomeScreen(onNavigateToProfile = { id ->
                navController.navigate(Profile(userId = id))
            })
        }
        composable<Profile> { backStackEntry ->
            val profile: Profile = backStackEntry.toRoute()
            ProfileScreen(userId = profile.userId)
        }
    }
}
使用新版Compose类型安全导航(在近期版本中可用)。
kotlin
// Define Destinations
@Serializable
object Home

@Serializable
data class Profile(val userId: String)

// Setup NavHost
@Composable
fun AppNavHost(navController: NavHostController) {
    NavHost(navController, startDestination = Home) {
        composable<Home> {
            HomeScreen(onNavigateToProfile = { id ->
                navController.navigate(Profile(userId = id))
            })
        }
        composable<Profile> { backStackEntry ->
            val profile: Profile = backStackEntry.toRoute()
            ProfileScreen(userId = profile.userId)
        }
    }
}

Best Practices

最佳实践

  • Do: Use
    remember
    and
    derivedStateOf
    to minimize unnecessary calculations during recomposition.
  • Do: Mark data classes used in UI state as
    @Immutable
    or
    @Stable
    if they contain
    List
    or other unstable types to enable smart recomposition skipping.
  • Do: Use
    LaunchedEffect
    for one-off side effects (like showing a Snackbar) triggered by state changes.
  • Don't: Perform expensive operations (like sorting a list) directly inside the Composable function body without
    remember
    .
  • Don't: Pass
    ViewModel
    instances down to child components. Pass only the data (state) and lambda callbacks (events).
  • 建议: 使用
    remember
    derivedStateOf
    来减少重组期间不必要的计算。
  • 建议: 如果UI状态中包含
    List
    或其他不稳定类型,将用于UI状态的数据类标记为
    @Immutable
    @Stable
    ,以支持智能跳过重组。
  • 建议: 使用
    LaunchedEffect
    处理由状态变化触发的一次性副作用(如显示Snackbar)。
  • 禁止: 在无
    remember
    的情况下,直接在可组合函数体中执行昂贵操作(如排序列表)。
  • 禁止:
    ViewModel
    实例向下传递给子组件。仅传递数据(状态)和Lambda回调(事件)。

Troubleshooting

故障排除

Problem: Infinite Recomposition loop. Solution: Check if you are creating new object instances (like
List
or
Modifier
) inside the composition without
remember
, or if you are updating state inside the composition phase instead of a side-effect or callback. Use Layout Inspector to debug recomposition counts.
问题: 无限重组循环。 解决方案: 检查是否在无
remember
的情况下在组合内部创建了新的对象实例(如
List
Modifier
),或者是否在组合阶段而非副作用或回调中更新状态。使用布局检查器调试重组次数。