名称: android-architecture 用户可调用: false 描述: 在实现MVVM、清洁架构、使用Hilt进行依赖注入或结构化Android应用层时使用。 允许的工具:
- 读取
- 写入
- 编辑
- Bash
- Grep
- Glob
Android - 架构
遵循Google推荐实践的现代Android架构模式。
关键概念
MVVM 架构
模型-视图-视图模型将UI与业务逻辑分离:
// UI 状态
data class UserUiState(
val user: User? = null,
val isLoading: Boolean = false,
val error: String? = null
)
// 视图模型
class UserViewModel(
private val userRepository: UserRepository
) : ViewModel() {
private val _uiState = MutableStateFlow(UserUiState())
val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()
fun loadUser(userId: String) {
viewModelScope.launch {
_uiState.update { it.copy(isLoading = true, error = null) }
userRepository.getUser(userId)
.onSuccess { user ->
_uiState.update { it.copy(user = user, isLoading = false) }
}
.onFailure { error ->
_uiState.update { it.copy(error = error.message, isLoading = false) }
}
}
}
}
// 可组合函数
@Composable
fun UserScreen(viewModel: UserViewModel = hiltViewModel()) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
when {
uiState.isLoading -> LoadingIndicator()
uiState.error != null -> ErrorMessage(uiState.error!!)
uiState.user != null -> UserContent(uiState.user!!)
}
}
清洁架构层
app/
├── data/
│ ├── local/ # Room 数据库, DataStore
│ │ ├── dao/
│ │ └── entities/
│ ├── remote/ # Retrofit, 网络
│ │ ├── api/
│ │ └── dto/
│ └── repository/ # 存储库实现
├── domain/
│ ├── model/ # 领域模型
│ ├── repository/ # 存储库接口
│ └── usecase/ # 业务逻辑
└── presentation/
├── ui/ # 可组合函数
└── viewmodel/ # 视图模型
存储库模式
// 领域层 - 接口
interface UserRepository {
fun getUser(id: String): Flow<User>
suspend fun saveUser(user: User): Result<Unit>
suspend fun deleteUser(id: String): Result<Unit>
}
// 数据层 - 实现
class UserRepositoryImpl(
private val userApi: UserApi,
private val userDao: UserDao
) : UserRepository {
override fun getUser(id: String): Flow<User> = flow {
// 首先发出缓存数据
userDao.getUser(id)?.let { emit(it.toDomain()) }
// 获取新数据
try {
val remoteUser = userApi.getUser(id)
userDao.insertUser(remoteUser.toEntity())
emit(remoteUser.toDomain())
} catch (e: Exception) {
// 网络错误,缓存数据已发出
}
}
override suspend fun saveUser(user: User): Result<Unit> = runCatching {
userApi.updateUser(user.toDto())
userDao.insertUser(user.toEntity())
}
}
最佳实践
使用Hilt进行依赖注入
// 模块定义
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
@Singleton
fun provideRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl(BuildConfig.API_BASE_URL)
.addConverterFactory(MoshiConverterFactory.create())
.build()
}
@Provides
@Singleton
fun provideUserApi(retrofit: Retrofit): UserApi {
return retrofit.create(UserApi::class.java)
}
}
@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {
@Binds
@Singleton
abstract fun bindUserRepository(impl: UserRepositoryImpl): UserRepository
}
// 视图模型注入
@HiltViewModel
class UserViewModel @Inject constructor(
private val getUserUseCase: GetUserUseCase,
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
private val userId: String = savedStateHandle.get<String>("userId")
?: throw IllegalArgumentException("userId required")
// 视图模型实现
}
业务逻辑用例
class GetUserUseCase @Inject constructor(
private val userRepository: UserRepository,
private val analyticsTracker: AnalyticsTracker
) {
operator fun invoke(userId: String): Flow<Result<User>> = flow {
emit(Result.Loading)
userRepository.getUser(userId)
.catch { e ->
analyticsTracker.trackError("get_user_failed", e)
emit(Result.Error(e))
}
.collect { user ->
emit(Result.Success(user))
}
}
}
// 结果的密封类
sealed class Result<out T> {
data class Success<T>(val data: T) : Result<T>()
data class Error(val exception: Throwable) : Result<Nothing>()
object Loading : Result<Nothing>()
}
Room 数据库设置
@Entity(tableName = "users")
data class UserEntity(
@PrimaryKey val id: String,
val name: String,
val email: String,
@ColumnInfo(name = "created_at") val createdAt: Long
)
@Dao
interface UserDao {
@Query("SELECT * FROM users WHERE id = :id")
suspend fun getUser(id: String): UserEntity?
@Query("SELECT * FROM users ORDER BY name ASC")
fun getAllUsers(): Flow<List<UserEntity>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertUser(user: UserEntity)
@Delete
suspend fun deleteUser(user: UserEntity)
}
@Database(entities = [UserEntity::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
// Hilt 模块
@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {
@Provides
@Singleton
fun provideDatabase(@ApplicationContext context: Context): AppDatabase {
return Room.databaseBuilder(
context,
AppDatabase::class.java,
"app_database"
).build()
}
@Provides
fun provideUserDao(database: AppDatabase): UserDao {
return database.userDao()
}
}
数据映射
// DTO(数据传输对象)- 来自API
data class UserDto(
@Json(name = "id") val id: String,
@Json(name = "full_name") val fullName: String,
@Json(name = "email_address") val email: String
)
// 实体 - 用于Room
@Entity(tableName = "users")
data class UserEntity(
@PrimaryKey val id: String,
val name: String,
val email: String
)
// 领域模型
data class User(
val id: String,
val name: String,
val email: String
)
// 映射器
fun UserDto.toEntity() = UserEntity(
id = id,
name = fullName,
email = email
)
fun UserDto.toDomain() = User(
id = id,
name = fullName,
email = email
)
fun UserEntity.toDomain() = User(
id = id,
name = name,
email = email
)
fun User.toEntity() = UserEntity(
id = id,
name = name,
email = email
)
常见模式
单一数据源
class OfflineFirstRepository @Inject constructor(
private val api: ItemApi,
private val dao: ItemDao
) : ItemRepository {
override fun getItems(): Flow<List<Item>> {
return dao.getAllItems()
.map { entities -> entities.map { it.toDomain() } }
.onStart {
// 在后台从网络刷新
refreshItems()
}
}
private suspend fun refreshItems() {
try {
val remoteItems = api.getItems()
dao.deleteAll()
dao.insertAll(remoteItems.map { it.toEntity() })
} catch (e: Exception) {
// 记录错误,本地数据仍可用
}
}
}
类型安全导航参数
// 定义路由
sealed class Screen(val route: String) {
object Home : Screen("home")
object Detail : Screen("detail/{itemId}") {
fun createRoute(itemId: String) = "detail/$itemId"
}
object Settings : Screen("settings")
}
// 导航设置
@Composable
fun AppNavigation(navController: NavHostController) {
NavHost(navController = navController, startDestination = Screen.Home.route) {
composable(Screen.Home.route) {
HomeScreen(
onItemClick = { itemId ->
navController.navigate(Screen.Detail.createRoute(itemId))
}
)
}
composable(
route = Screen.Detail.route,
arguments = listOf(navArgument("itemId") { type = NavType.StringType })
) { backStackEntry ->
DetailScreen(
itemId = backStackEntry.arguments?.getString("itemId") ?: return@composable
)
}
}
}
错误处理
sealed class UiState<out T> {
object Loading : UiState<Nothing>()
data class Success<T>(val data: T) : UiState<T>()
data class Error(val message: String, val retry: (() -> Unit)? = null) : UiState<Nothing>()
}
@Composable
fun <T> StateHandler(
state: UiState<T>,
onRetry: () -> Unit = {},
content: @Composable (T) -> Unit
) {
when (state) {
is UiState.Loading -> {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
CircularProgressIndicator()
}
}
is UiState.Error -> {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(state.message)
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = onRetry) {
Text("重试")
}
}
}
is UiState.Success -> content(state.data)
}
}
反模式
上帝Activity/Fragment
坏:所有逻辑在一个Activity中。
好:使用MVVM,职责清晰分离。
在ViewModel中进行网络调用
坏:
class BadViewModel : ViewModel() {
fun loadData() {
val client = OkHttpClient() // 直接网络依赖
// ...
}
}
好:通过构造函数注入存储库。
暴露可变状态
坏:
class BadViewModel : ViewModel() {
val uiState = MutableStateFlow(UiState()) // 可变暴露!
}
好:
class GoodViewModel : ViewModel() {
private val _uiState = MutableStateFlow(UiState())
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
}
相关技能
- android-jetpack-compose: UI层模式
- android-kotlin-coroutines: 异步操作