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架构:

  1. 架构设计原则
  2. 数据层设计
  3. ViewModel层设计
  4. View层实现
  5. 依赖注入配置
  6. 单元测试
  7. 最佳实践

MVVM是Android推荐的架构模式,合理使用可以提高代码质量和可维护性。

最后更新:2026-03-26