一句话总结:
工业级的网络请求,不是在 ViewModel 中零散地 try-catch,而是通过构建一个标准的 Result 封装、一个可复用的 safeApiCall 和一个完备的 UiState 模型,将网络请求的加载、成功、失败状态,以及精细化的错误类型,优雅地传递给 UI 层。
第一章:坚实的基础——现代化的 Retrofit + 协程请求
你的文章已经出色地展示了这一基础:使用 suspend 函数定义 API,并在 ViewModel 中通过 viewModelScope 发起请求。这是我们构建一切的起点。
// ApiService.kt
interface ApiService {
@GET("users/{id}")
suspend fun getUser(@Path("id") userId: Int): User
}
// UserViewModel.kt (基础版)
class UserViewModel : ViewModel() {
fun fetchUser() {
viewModelScope.launch {
try {
val user = apiService.getUser(1)
// ... 成功 ...
} catch (e: Exception) {
// ... 失败 ...
}
}
}
}
这个模型能工作,但它脆弱、粗糙且充满了重复代码。现在,我们开始为它“添砖加瓦”,将其打造成坚固的“城堡”。
第二章:架构的三大支柱
支柱一:用 Result 封装“结果”,告别分散的 try-catch
我们首先定义一个标准的、密封的 Result 类,用来封装所有数据请求的两种可能结果:成功或失败。
sealed class Result<out T> {
data class Success<out T>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
}
现在,我们要求所有 Repository 的方法,返回的不再是 User,而是 Result<User>。这使得“失败”成为了一种可被类型系统识别的一等公民。
支柱二:用 BaseRepository 抽象“过程”,消除重复代码
创建一个 BaseRepository,在其中定义一个可被所有子类复用的 safeApiCall 函数。这个函数将成为我们所有网络请求的“安全外壳”。
// BaseRepository.kt
abstract class BaseRepository {
suspend fun <T> safeApiCall(apiCall: suspend () -> T): Result<T> {
return withContext(Dispatchers.IO) {
try {
Result.Success(apiCall.invoke())
} catch (e: Exception) {
Result.Error(e)
}
}
}
}
现在,我们的 UserRepository 变得极其干净:
// UserRepository.kt
class UserRepository(private val apiService: ApiService) : BaseRepository() {
suspend fun getUser(id: Int): Result<User> {
// 只关心业务调用,所有 try-catch 和线程切换都已封装
return safeApiCall { apiService.getUser(id) }
}
}
支柱三:用 UiState 表达“状态”,让 UI 响应更完整
UI 需要知道的不仅是成功和失败,还有“加载中”。因此,我们在 ViewModel 中定义一个 UiState。
// UserViewModel.kt
sealed class UserUiState {
object Loading : UserUiState()
data class Success(val user: User) : UserUiState()
data class Error(val message: String) : UserUiState()
}
第三章:完整的工业级数据流
现在,我们将三大支柱串联起来,形成一个清晰、健壮的数据流动管道。
// UserViewModel.kt
class UserViewModel(private val repository: UserRepository) : ViewModel() {
private val _uiState = MutableStateFlow<UserUiState>(UserUiState.Loading)
val uiState: StateFlow<UserUiState> = _uiState
fun fetchUser(id: Int) {
// 1. UI 进入 Loading 状态
_uiState.value = UserUiState.Loading
viewModelScope.launch {
// 2. Repository 返回标准化的 Result
val result = repository.getUser(id)
// 3. ViewModel 根据 Result 更新 UiState
when (result) {
is Result.Success -> {
_uiState.value = UserUiState.Success(result.data)
}
is Result.Error -> {
// 4. 在这里进行精细化的错误处理
val errorMessage = parseError(result.exception)
_uiState.value = UserUiState.Error(errorMessage)
}
}
}
}
// 统一的错误解析逻辑
private fun parseError(exception: Exception): String {
return when (exception) {
is HttpException -> "服务器错误: ${exception.code()}"
is IOException -> "网络连接失败"
else -> "未知错误"
}
}
}
最后,Activity 的职责变得前所未有的纯粹:
// MainActivity.kt
lifecycleScope.launch {
viewModel.uiState.collect { state ->
when (state) {
is UserUiState.Loading -> showProgressBar()
is UserUiState.Success -> showUserData(state.user)
is UserUiState.Error -> showError(state.message)
}
}
}
四、总结:从“能用”到“可靠”的架构演进
| 维度 | 基础实践 (能用) | 工业级架构 (可靠) |
|---|---|---|
| 返回类型 | suspend fun getData(): Data | suspend fun getData(): Result<Data> |
| 错误处理 | 在 ViewModel 中分散 try-catch | 在 BaseRepository 中统一处理,返回 Result.Error |
| UI 状态 | 只有成功/失败两种状态 | 包含 Loading, Success, Error 的 UiState |
| 代码复用 | 每个请求都重复 try-catch, withContext | 通过 safeApiCall 消除所有样板代码 |
通过这套架构,我们构建的不再是一次性的“外卖订单”,而是一个标准化的、可复用的、高度健壮的“中央厨房” ,能够为应用的任何部分,稳定地提供数据成品。