Skip to content

Result 统一结果流

app/src/main/java/com/joker/kit/core/result 聚合了网络请求的三段式状态封装:Result.kt 定义 Loading/Success/Error,ResultExt.kt 负责把任意 Flow 转成 ResultResultHandler.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.Loadingobject请求开始或仍在加载中
Result.Success<T>data class请求成功,携带 data
Result.Errordata class请求失败或解析异常,包含 Throwable

Flow<T>.asResult()

参数类型默认值说明
thisFlow<T>-原始网络或数据库 Flow
onStart 自动发射 Result.Loadingmap 包装为 Result.Successcatch 捕获异常转为 Result.Error

ResultHandler.handleResult

参数类型默认值说明
scopeCoroutineScope-一般传入 viewModelScope,内部会调用 launch 收集 Flow
flowFlow<Result<NetworkResponse<T>>>-已通过 asResult 注入状态的 Flow
showToastBooleantrue控制 ToastUtils 是否自动弹出错误提示
onLoading() -> Unit{}请求发起前即会触发,可在此显示/重置 Loading,也可以留空
onSuccess(NetworkResponse<T>) -> Unit{}收到 Result.Success 时立即回调,适合需要读取接口自定义 code/message 的场景
onSuccessWithData(T) -> Unit{}仅当 response.isSucceededdata != null 时触发
onError(String, Throwable?) -> Unit{ _, _ -> }收到 Result.Error 或业务失败时调用,ResultHandler 会将 message、异常抛给你处理
onFinally() -> Unit{}Flow 结束后一定执行,可在此恢复按钮可点击状态

ResultHandler.handleResultWithData

参数类型默认值说明
scopeCoroutineScope-handleResult
flowFlow<Result<NetworkResponse<T>>>-handleResult
showToastBooleantrue是否自动弹出错误提示
onLoading() -> Unit{}请求开始时触发的 Loading 回调,可选
onData(T) -> Unit-仅当请求成功且 data 不为空时才会被调用
onError(String, Throwable?) -> Unit{ _, _ -> }错误回调
onFinally() -> Unit{}Flow 收集结束必定调用

设计约定

  • NetworkResponse.isSucceeded 判定成功(当前 code==1000);失败会自动 Toast(可用 showToast=false 关闭)。
  • 日志集中在 ResultHandlerTimber.e 打印完整堆栈,方便排查。
  • UI/VM 都不关心线程切换,仓库内自行 flowOn(Dispatchers.IO)

常见误区 & 提示

  • 忘记 asResult:直接把 Flow<NetworkResponse> 传给 ResultHandler 会导致没有 Loading/Error 包裹,务必 .asResult()
  • 需要原始响应:用 handleResult,在 onSuccess 里读 code/message,在 onSuccessWithData 里拿到 data
  • 无需 Toast:调用时 showToast = false

参考文件

  • Result.ktResult.Loading / Success / Error 三种状态
  • ResultExt.ktFlow<T>.asResult()
  • ResultHandler.kt:统一收集、Toast、日志
  • 示例:feature/demo/viewmodel/NetworkRequestViewModel.ktfeature/demo/view/NetworkRequestScreen.kt