技术文档中心
首页
React
Vue
TypeScript
Kotlin
React Native
Electron
Android
首页
React
Vue
TypeScript
Kotlin
React Native
Electron
Android
  • 基础入门

    • Android 开发指南
    • Kotlin 基础
  • 进阶内容

    • Jetpack 组件
    • MVVM 架构
    • Jetpack Compose

MVVM 架构

架构概述

View (Activity/Fragment)
    ↓
ViewModel
    ↓
Repository
    ↓
Data Source (API/Database)

完整示例

Model

data class User(
    val id: Int,
    val name: String,
    val email: String
)

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>()
}

Repository

class UserRepository(
    private val api: ApiService,
    private val dao: UserDao
) {
    suspend fun getUsers(): Result<List<User>> {
        return try {
            val users = api.getUsers()
            dao.insertAll(users)
            Result.success(users)
        } catch (e: Exception) {
            val cachedUsers = dao.getAll()
            if (cachedUsers.isNotEmpty()) {
                Result.success(cachedUsers)
            } else {
                Result.failure(e)
            }
        }
    }

    suspend fun getUserById(id: Int): User? {
        return dao.getById(id) ?: api.getUserById(id)
    }

    suspend fun createUser(user: User): Result<User> {
        return try {
            val newUser = api.createUser(user)
            dao.insert(newUser)
            Result.success(newUser)
        } catch (e: Exception) {
            Result.failure(e)
        }
    }
}

ViewModel

class UserViewModel(
    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()
                .onSuccess { users ->
                    _uiState.value = UiState.Success(users)
                }
                .onFailure { error ->
                    _uiState.value = UiState.Error(error.message ?: "未知错误")
                }
        }
    }

    fun selectUser(userId: Int) {
        viewModelScope.launch {
            val user = repository.getUserById(userId)
            _selectedUser.value = user
        }
    }

    fun createUser(name: String, email: String) {
        viewModelScope.launch {
            val user = User(0, name, email)
            repository.createUser(user)
                .onSuccess {
                    loadUsers()
                }
                .onFailure {
                    _uiState.value = UiState.Error("创建失败")
                }
        }
    }
}

View (Activity)

@AndroidEntryPoint
class UserActivity : AppCompatActivity() {

    private val viewModel: UserViewModel by viewModels()
    private lateinit var binding: ActivityUserBinding
    private val adapter = UserAdapter()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityUserBinding.inflate(layoutInflater)
        setContentView(binding.root)

        setupRecyclerView()
        observeUiState()
        setupListeners()
    }

    private fun setupRecyclerView() {
        binding.recyclerView.apply {
            layoutManager = LinearLayoutManager(this@UserActivity)
            adapter = this@UserActivity.adapter
        }
    }

    private fun observeUiState() {
        lifecycleScope.launch {
            viewModel.uiState.collect { state ->
                when (state) {
                    is UiState.Loading -> showLoading()
                    is UiState.Success -> showUsers(state.data)
                    is UiState.Error -> showError(state.message)
                }
            }
        }
    }

    private fun setupListeners() {
        binding.btnRefresh.setOnClickListener {
            viewModel.loadUsers()
        }

        adapter.onItemClick = { user ->
            viewModel.selectUser(user.id)
        }
    }

    private fun showLoading() {
        binding.progressBar.visibility = View.VISIBLE
        binding.recyclerView.visibility = View.GONE
        binding.errorView.visibility = View.GONE
    }

    private fun showUsers(users: List<User>) {
        binding.progressBar.visibility = View.GONE
        binding.recyclerView.visibility = View.VISIBLE
        binding.errorView.visibility = View.GONE
        adapter.submitList(users)
    }

    private fun showError(message: String) {
        binding.progressBar.visibility = View.GONE
        binding.recyclerView.visibility = View.GONE
        binding.errorView.visibility = View.VISIBLE
        binding.errorText.text = message
    }
}

Adapter

class UserAdapter : ListAdapter<User, UserAdapter.ViewHolder>(UserDiffCallback()) {

    var onItemClick: ((User) -> Unit)? = null

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val binding = ItemUserBinding.inflate(
            LayoutInflater.from(parent.context),
            parent,
            false
        )
        return ViewHolder(binding)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(getItem(position))
    }

    inner class ViewHolder(
        private val binding: ItemUserBinding
    ) : RecyclerView.ViewHolder(binding.root) {

        init {
            binding.root.setOnClickListener {
                val position = bindingAdapterPosition
                if (position != RecyclerView.NO_POSITION) {
                    onItemClick?.invoke(getItem(position))
                }
            }
        }

        fun bind(user: User) {
            binding.apply {
                tvName.text = user.name
                tvEmail.text = user.email
            }
        }
    }

    class UserDiffCallback : DiffUtil.ItemCallback<User>() {
        override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
            return oldItem.id == newItem.id
        }

        override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
            return oldItem == newItem
        }
    }
}

依赖注入(Hilt)

Module

@Module
@InstallIn(SingletonComponent::class)
object AppModule {

    @Provides
    @Singleton
    fun provideRetrofit(): Retrofit {
        return Retrofit.Builder()
            .baseUrl("https://api.example.com/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }

    @Provides
    fun provideApiService(retrofit: Retrofit): ApiService {
        return retrofit.create(ApiService::class.java)
    }

    @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()
    }

    @Provides
    fun provideUserRepository(
        api: ApiService,
        dao: UserDao
    ): UserRepository {
        return UserRepository(api, dao)
    }
}

ViewModel Factory

@HiltViewModel
class UserViewModel @Inject constructor(
    private val repository: UserRepository
) : ViewModel() {
    // ...
}

最佳实践

单一数据源

class UserRepository(
    private val api: ApiService,
    private val dao: UserDao
) {
    fun getUsers(): Flow<List<User>> = flow {
        // 先发送缓存数据
        emit(dao.getAll())
        
        // 然后获取网络数据
        try {
            val users = api.getUsers()
            dao.insertAll(users)
            emit(users)
        } catch (e: Exception) {
            // 网络失败,使用缓存
        }
    }
}

错误处理

sealed class Resource<T> {
    data class Success<T>(val data: T) : Resource<T>()
    data class Error<T>(val message: String, val data: T? = null) : Resource<T>()
    class Loading<T> : Resource<T>()
}

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

    private val _users = MutableStateFlow<Resource<List<User>>>(Resource.Loading())
    val users: StateFlow<Resource<List<User>>> = _users

    fun loadUsers() {
        viewModelScope.launch {
            _users.value = Resource.Loading()
            
            try {
                val result = repository.getUsers()
                _users.value = Resource.Success(result)
            } catch (e: Exception) {
                _users.value = Resource.Error(e.message ?: "未知错误")
            }
        }
    }
}

分页加载

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

    val users: Flow<PagingData<User>> = Pager(
        config = PagingConfig(pageSize = 20),
        pagingSourceFactory = { repository.getUserPagingSource() }
    ).flow.cachedIn(viewModelScope)
}
最近更新: 2026/2/24 16:53
Contributors: hailong
Prev
Jetpack 组件
Next
Jetpack Compose