Result 统一结果流
app/src/main/java/com/joker/kit/core/result 聚合了网络请求的三段式状态封装:Result.kt 定义 Loading/Success/Error,ResultExt.kt 负责把任意 Flow 转成 Result,ResultHandler.kt 集中处理 Loading、数据回填、错误提示与日志。业务 ViewModel 只需关注数据流,不再重复写 try/catch 和 Toast。
使用示例(轻量版:只要成功数据)
以商品详情为例,仓库返回 Flow<NetworkResponse<Goods>>,ViewModel 用 asResult 注入状态,再交给 ResultHandler 统一分发:
kotlin
// GoodsRepository.kt
fun getGoodsInfo(id: String): Flow<NetworkResponse<Goods>> =
flow { emit(goodsNetworkDataSource.getGoodsInfo(id)) }
.flowOn(Dispatchers.IO)kotlin
@HiltViewModel
class GoodsDetailViewModel @Inject constructor(
private val goodsRepository: GoodsRepository
) : ViewModel() {
private val _uiState = MutableStateFlow<BaseNetWorkUiState<Goods>>(BaseNetWorkUiState.Loading)
val uiState: StateFlow<BaseNetWorkUiState<Goods>> = _uiState
fun loadDetail(id: String) {
ResultHandler.handleResultWithData(
scope = viewModelScope, // 统一作用域
flow = goodsRepository.getGoodsInfo(id).asResult(), // 注入 Loading/Error
onLoading = { _uiState.value = BaseNetWorkUiState.Loading },
onData = { goods -> _uiState.value = BaseNetWorkUiState.Success(goods) },
onError = { message, throwable ->
_uiState.value = BaseNetWorkUiState.Error(message, throwable)
}
)
}
}UI 对应参考:feature/demo/viewmodel/NetworkRequestViewModel.kt + feature/demo/view/NetworkRequestScreen.kt(发起请求按钮,goods != null 时展示卡片)。
使用示例(完整版:需要 code/message)
当你需要读取接口的 code/message 再决定 UI,改用 handleResult:
kotlin
ResultHandler.handleResult(
scope = viewModelScope,
flow = goodsRepository.getGoodsInfo(id).asResult(),
onLoading = { _uiState.value = BaseNetWorkUiState.Loading },
onSuccess = { resp -> // 拿到原始响应
_uiState.value = if (resp.isSucceeded && resp.data != null) {
BaseNetWorkUiState.Success(resp.data)
} else {
BaseNetWorkUiState.Error(resp.message ?: "业务失败")
}
},
onSuccessWithData = { data -> _uiState.value = BaseNetWorkUiState.Success(data) },
onError = { msg, t -> _uiState.value = BaseNetWorkUiState.Error(msg, t) },
onFinally = { /* 收尾:复位按钮/Loading */ }
)API 参考
Result<T>
| 名称 | 类型 | 说明 |
|---|---|---|
Result.Loading | object | 请求开始或仍在加载中 |
Result.Success<T> | data class | 请求成功,携带 data |
Result.Error | data class | 请求失败或解析异常,包含 Throwable |
Flow<T>.asResult()
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
this | Flow<T> | - | 原始网络或数据库 Flow |
| — | — | — | onStart 自动发射 Result.Loading,map 包装为 Result.Success,catch 捕获异常转为 Result.Error |
ResultHandler.handleResult
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
scope | CoroutineScope | - | 一般传入 viewModelScope,内部会调用 launch 收集 Flow |
flow | Flow<Result<NetworkResponse<T>>> | - | 已通过 asResult 注入状态的 Flow |
showToast | Boolean | true | 控制 ToastUtils 是否自动弹出错误提示 |
onLoading | () -> Unit | {} | 请求发起前即会触发,可在此显示/重置 Loading,也可以留空 |
onSuccess | (NetworkResponse<T>) -> Unit | {} | 收到 Result.Success 时立即回调,适合需要读取接口自定义 code/message 的场景 |
onSuccessWithData | (T) -> Unit | {} | 仅当 response.isSucceeded 且 data != null 时触发 |
onError | (String, Throwable?) -> Unit | { _, _ -> } | 收到 Result.Error 或业务失败时调用,ResultHandler 会将 message、异常抛给你处理 |
onFinally | () -> Unit | {} | Flow 结束后一定执行,可在此恢复按钮可点击状态 |
ResultHandler.handleResultWithData
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
scope | CoroutineScope | - | 同 handleResult |
flow | Flow<Result<NetworkResponse<T>>> | - | 同 handleResult |
showToast | Boolean | true | 是否自动弹出错误提示 |
onLoading | () -> Unit | {} | 请求开始时触发的 Loading 回调,可选 |
onData | (T) -> Unit | - | 仅当请求成功且 data 不为空时才会被调用 |
onError | (String, Throwable?) -> Unit | { _, _ -> } | 错误回调 |
onFinally | () -> Unit | {} | Flow 收集结束必定调用 |
设计约定
NetworkResponse.isSucceeded判定成功(当前 code==1000);失败会自动 Toast(可用showToast=false关闭)。- 日志集中在
ResultHandler用Timber.e打印完整堆栈,方便排查。 - UI/VM 都不关心线程切换,仓库内自行
flowOn(Dispatchers.IO)。
常见误区 & 提示
- 忘记 asResult:直接把
Flow<NetworkResponse>传给ResultHandler会导致没有 Loading/Error 包裹,务必.asResult()。 - 需要原始响应:用
handleResult,在onSuccess里读code/message,在onSuccessWithData里拿到data。 - 无需 Toast:调用时
showToast = false。
参考文件
Result.kt:Result.Loading / Success / Error三种状态ResultExt.kt:Flow<T>.asResult()ResultHandler.kt:统一收集、Toast、日志- 示例:
feature/demo/viewmodel/NetworkRequestViewModel.kt、feature/demo/view/NetworkRequestScreen.kt