如何合理编写Android ViewModel并迁移其中的业务逻辑?
我正在Android/Kotlin中使用MVVM、ViewModel、ViewBinding和Retrofit2开发。我不知道如何将业务逻辑从ViewModel中迁移出去——这些逻辑运行在viewModelScope中,结果还要存入ViewModel里的可观察对象(比如LiveData)。但或许其实是可以做到的?
举个例子,我有一个用于ListView展示的ArrayList:
// items ArrayList private val _stocktakings = MutableLiveData<ArrayList<InventoryStocktakingWithCountsDto?>>(ArrayList()) val stocktakings : LiveData<ArrayList<InventoryStocktakingWithCountsDto?>> get() = _stocktakings // selected item private val _selected_stocktaking = MutableLiveData<Int>() val selected_stocktaking : LiveData<Int> get() = _selected_stocktaking
还有一个从Fragment中调用的函数:
public fun loadStocktakings() { viewModelScope.launch { Log.d(TAG, "Load stocktakings requested") clearError() try { with(ApiResponse(ApiAdapter.apiClient.findAllStocktakings())){ if (isSuccessful && body != null){ Log.d(TAG, "Load Stocktakings done") setStocktakings(body) } else { val e = "Can't load stocktakings, API error: {${errorMessage}}" Log.e(TAG, e) HandleError("Can't load stocktakings, API error: {${e}}") // 将错误信息存入lastError这个MutableLiveData... } } } catch (t : Throwable) { Log.e(TAG, "Can't load stocktakings, connectivity error: ${t.message}") HandleError("Can't load stocktakings, API error: {${e}}") // 将错误信息存入lastError这个MutableLiveData... } } }
现在我想添加另一个函数来修改stocktakings中的某个字段,大概是这样:
public fun setSelectedStocktakingComplete() { stocktakings.value[selected_stocktaking.value].isComplete = true; // 调用某个API函数... 又要写15行代码? }
正确的做法是什么?
我感觉自己看了错误的教程,这样下去ViewModel会变得臃肿不堪,全是viewModelScope.launch、错误处理代码,我甚至不敢想象添加数据/表单验证后会变成什么样……
1. 用Repository层封装数据操作
把所有和API、本地数据相关的逻辑抽离到独立的Repository类中,ViewModel只负责调用Repository的方法,不直接处理Retrofit请求或数据解析。
示例StocktakingRepository:
class StocktakingRepository(private val apiClient: ApiClient) { suspend fun loadAllStocktakings(): Result<List<InventoryStocktakingWithCountsDto>> { return try { val response = apiClient.findAllStocktakings() if (response.isSuccessful && response.body() != null) { Result.success(response.body()!!) } else { Result.failure(Exception("API错误: ${response.errorMessage}")) } } catch (t: Throwable) { Result.failure(Exception("网络连接错误: ${t.message}")) } } suspend fun markStocktakingAsComplete(stocktakingId: Int): Result<Unit> { // 这里实现调用API标记完成的逻辑,用Result统一封装结果 return try { val response = apiClient.markStocktakingComplete(stocktakingId) if (response.isSuccessful) { Result.success(Unit) } else { Result.failure(Exception("标记完成失败: ${response.errorMessage}")) } } catch (t: Throwable) { Result.failure(Exception("网络连接错误: ${t.message}")) } } }
2. 用UseCase封装单一业务逻辑(可选但推荐)
如果业务逻辑复杂(比如表单验证、多数据源组合),可以再加一层UseCase,把每个独立的业务逻辑封装成单独的UseCase类,ViewModel调用UseCase,UseCase再调用Repository。
示例UseCase:
class LoadStocktakingsUseCase(private val repository: StocktakingRepository) { suspend operator fun invoke(): Result<List<InventoryStocktakingWithCountsDto>> { // 这里可添加额外业务逻辑,比如数据过滤、缓存校验等 return repository.loadAllStocktakings() } } class MarkStocktakingCompleteUseCase(private val repository: StocktakingRepository) { suspend operator fun invoke(stocktakingId: Int): Result<Unit> { // 前置验证逻辑,比如检查ID是否合法 if (stocktakingId < 0) { return Result.failure(Exception("无效的库存盘点ID")) } return repository.markStocktakingAsComplete(stocktakingId) } }
3. ViewModel简化为协调者角色
ViewModel只负责以下核心工作:
- 持有LiveData,向View层暴露状态
- 调用Repository/UseCase获取数据
- 根据返回结果更新LiveData和错误状态
改造后的ViewModel:
class StocktakingViewModel( private val loadStocktakingsUseCase: LoadStocktakingsUseCase, private val markCompleteUseCase: MarkStocktakingCompleteUseCase ) : ViewModel() { private val _stocktakings = MutableLiveData<List<InventoryStocktakingWithCountsDto>>(emptyList()) val stocktakings: LiveData<List<InventoryStocktakingWithCountsDto>> get() = _stocktakings private val _selectedStocktaking = MutableLiveData<Int>() val selectedStocktaking: LiveData<Int> get() = _selectedStocktaking private val _lastError = MutableLiveData<String?>() val lastError: LiveData<String?> get() = _lastError fun loadStocktakings() { viewModelScope.launch { _lastError.value = null when (val result = loadStocktakingsUseCase()) { is Result.Success -> _stocktakings.value = result.data is Result.Failure -> _lastError.value = result.exception.message } } } fun setSelectedStocktakingComplete() { viewModelScope.launch { _lastError.value = null val selectedId = _selectedStocktaking.value ?: return@launch when (val result = markCompleteUseCase(selectedId)) { is Result.Success -> { // 更新本地LiveData中的数据,注意用不可变集合保证数据安全 val currentList = _stocktakings.value?.toMutableList() currentList?.find { it?.id == selectedId }?.isComplete = true _stocktakings.value = currentList } is Result.Failure -> _lastError.value = result.exception.message } } } }
4. 封装通用逻辑减少重复
把viewModelScope中的重复逻辑(比如清空错误、处理Result)封装成扩展函数,进一步简化ViewModel代码:
fun <T> ViewModel.launchWithErrorHandling( errorLiveData: MutableLiveData<String?>, block: suspend () -> Result<T>, onSuccess: (T) -> Unit ) { viewModelScope.launch { errorLiveData.value = null when (val result = block()) { is Result.Success -> onSuccess(result.data) is Result.Failure -> errorLiveData.value = result.exception.message ?: "未知错误" } } }
使用扩展函数后,ViewModel的方法可以简化为:
fun loadStocktakings() { launchWithErrorHandling(_lastError, loadStocktakingsUseCase) { data -> _stocktakings.value = data } }
5. 优化LiveData的数据结构
把ArrayList换成不可变的List,避免外部直接修改数据,确保所有数据更新都通过ViewModel的方法触发,保证数据流的可控性和一致性。
内容的提问来源于stack exchange,提问作者Kamil

