MVVM架构 #
一、架构概述 #
1.1 为什么需要架构 #
- 代码可维护性
- 可测试性
- 关注点分离
- 代码复用
1.2 MVVM架构图 #
text
┌─────────────────────────────────────────────────────────┐
│ View Layer │
│ Activity / Fragment / Compose │
│ │ │
│ ▼ │
│ ViewModel │
├─────────────────────────────────────────────────────────┤
│ Data Layer │
│ Repository │
│ / \ │
│ ▼ ▼ │
│ Local Data Remote Data │
│ (Room/SQLite) (API/Network) │
└─────────────────────────────────────────────────────────┘
二、数据层 #
2.1 数据源接口 #
kotlin
interface UserDataSource {
suspend fun getUsers(): List<User>
suspend fun getUserById(id: String): User?
suspend fun saveUser(user: User)
}
2.2 本地数据源 #
kotlin
class UserLocalDataSource(
private val userDao: UserDao
) : UserDataSource {
override suspend fun getUsers(): List<User> {
return userDao.getAll()
}
override suspend fun getUserById(id: String): User? {
return userDao.getById(id)
}
override suspend fun saveUser(user: User) {
userDao.insert(user)
}
}
2.3 远程数据源 #
kotlin
class UserRemoteDataSource(
private val apiService: ApiService
) : UserDataSource {
override suspend fun getUsers(): List<User> {
return apiService.getUsers()
}
override suspend fun getUserById(id: String): User? {
return apiService.getUserById(id)
}
override suspend fun saveUser(user: User) {
apiService.createUser(user)
}
}
2.4 Repository #
kotlin
class UserRepository @Inject constructor(
private val localDataSource: UserLocalDataSource,
private val remoteDataSource: UserRemoteDataSource
) {
fun getUsers(): Flow<List<User>> = flow {
// 先返回本地数据
val localUsers = localDataSource.getUsers()
emit(localUsers)
// 从网络获取最新数据
try {
val remoteUsers = remoteDataSource.getUsers()
// 保存到本地
remoteUsers.forEach { localDataSource.saveUser(it) }
// 返回最新数据
emit(remoteUsers)
} catch (e: Exception) {
// 网络错误,继续使用本地数据
}
}
suspend fun getUserById(id: String): Result<User> {
return try {
val user = remoteDataSource.getUserById(id)
if (user != null) {
localDataSource.saveUser(user)
Result.success(user)
} else {
Result.failure(Exception("User not found"))
}
} catch (e: Exception) {
// 网络错误,尝试从本地获取
val localUser = localDataSource.getUserById(id)
if (localUser != null) {
Result.success(localUser)
} else {
Result.failure(e)
}
}
}
}
三、ViewModel层 #
3.1 UI状态 #
kotlin
sealed class UiState<out T> {
object Loading : UiState<Nothing>()
data class Success<T>(val data: T) : UiState<T>()
data class Error(val message: String) : UiState<Nothing>()
}
3.2 ViewModel #
kotlin
@HiltViewModel
class UserViewModel @Inject constructor(
private val repository: UserRepository
) : ViewModel() {
private val _uiState = MutableStateFlow<UiState<List<User>>>(UiState.Loading)
val uiState: StateFlow<UiState<List<User>>> = _uiState.asStateFlow()
private val _selectedUser = MutableStateFlow<User?>(null)
val selectedUser: StateFlow<User?> = _selectedUser.asStateFlow()
init {
loadUsers()
}
fun loadUsers() {
viewModelScope.launch {
_uiState.value = UiState.Loading
repository.getUsers()
.catch { e ->
_uiState.value = UiState.Error(e.message ?: "Unknown error")
}
.collect { users ->
_uiState.value = UiState.Success(users)
}
}
}
fun selectUser(user: User) {
_selectedUser.value = user
}
fun refresh() {
loadUsers()
}
}
四、View层 #
4.1 Activity #
kotlin
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val viewModel: UserViewModel by viewModels()
private lateinit var binding: ActivityMainBinding
private lateinit var adapter: UserAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
setupRecyclerView()
observeViewModel()
binding.swipeRefresh.setOnRefreshListener {
viewModel.refresh()
}
}
private fun setupRecyclerView() {
adapter = UserAdapter { user ->
viewModel.selectUser(user)
}
binding.recyclerView.adapter = adapter
}
private fun observeViewModel() {
lifecycleScope.launch {
viewModel.uiState.collect { state ->
when (state) {
is UiState.Loading -> {
binding.progressBar.isVisible = true
binding.swipeRefresh.isRefreshing = true
}
is UiState.Success -> {
binding.progressBar.isVisible = false
binding.swipeRefresh.isRefreshing = false
adapter.submitList(state.data)
}
is UiState.Error -> {
binding.progressBar.isVisible = false
binding.swipeRefresh.isRefreshing = false
showError(state.message)
}
}
}
}
}
private fun showError(message: String) {
Snackbar.make(binding.root, message, Snackbar.LENGTH_LONG).show()
}
}
4.2 Fragment #
kotlin
@AndroidEntryPoint
class UserDetailFragment : Fragment() {
private val viewModel: UserViewModel by activityViewModels()
private var _binding: FragmentUserDetailBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentUserDetailBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch {
viewModel.selectedUser.collect { user ->
user?.let { showUser(it) }
}
}
}
private fun showUser(user: User) {
binding.name.text = user.name
binding.email.text = user.email
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
五、依赖注入 #
5.1 模块配置 #
kotlin
@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()
}
}
@Module
@InstallIn(SingletonComponent::class)
object RepositoryModule {
@Provides
@Singleton
fun provideUserRepository(
localDataSource: UserLocalDataSource,
remoteDataSource: UserRemoteDataSource
): UserRepository {
return UserRepository(localDataSource, remoteDataSource)
}
}
六、测试 #
6.1 ViewModel测试 #
kotlin
@ExperimentalCoroutinesApi
class UserViewModelTest {
@get:Rule
val dispatcherRule = StandardTestDispatcher()
private lateinit var repository: UserRepository
private lateinit var viewModel: UserViewModel
@Before
fun setup() {
repository = mockk()
viewModel = UserViewModel(repository)
}
@Test
fun `loadUsers should emit success state`() = runTest {
// Given
val users = listOf(User("1", "Test"))
coEvery { repository.getUsers() } returns flowOf(users)
// When
viewModel.loadUsers()
advanceUntilIdle()
// Then
val state = viewModel.uiState.value
assertTrue(state is UiState.Success)
assertEquals(users, (state as UiState.Success).data)
}
}
6.2 Repository测试 #
kotlin
class UserRepositoryTest {
private lateinit var localDataSource: UserLocalDataSource
private lateinit var remoteDataSource: UserRemoteDataSource
private lateinit var repository: UserRepository
@Before
fun setup() {
localDataSource = mockk()
remoteDataSource = mockk()
repository = UserRepository(localDataSource, remoteDataSource)
}
@Test
fun `getUsers should return local data first`() = runTest {
// Given
val localUsers = listOf(User("1", "Local"))
val remoteUsers = listOf(User("1", "Remote"))
coEvery { localDataSource.getUsers() } returns localUsers
coEvery { remoteDataSource.getUsers() } returns remoteUsers
// When
val result = repository.getUsers().first()
// Then
assertEquals(localUsers, result)
}
}
七、最佳实践 #
7.1 单一职责 #
kotlin
// ViewModel只处理UI逻辑
class UserViewModel : ViewModel() {
// 不应该包含业务逻辑
}
// Repository处理数据逻辑
class UserRepository {
// 处理数据获取和缓存
}
7.2 使用密封类表示状态 #
kotlin
sealed class UiState<out T> {
object Loading : UiState<Nothing>()
data class Success<T>(val data: T) : UiState<T>()
data class Error(val message: String) : UiState<Nothing>()
}
7.3 避免在ViewModel中持有View引用 #
kotlin
// 错误
class MyViewModel(private val view: View) : ViewModel()
// 正确
class MyViewModel : ViewModel() {
private val _data = MutableStateFlow<String>("")
val data: StateFlow<String> = _data
}
八、总结 #
本章详细介绍了MVVM架构:
- 架构设计原则
- 数据层设计
- ViewModel层设计
- View层实现
- 依赖注入配置
- 单元测试
- 最佳实践
MVVM是Android推荐的架构模式,合理使用可以提高代码质量和可维护性。
最后更新:2026-03-26