You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Sandwich库是否有RxJava Single等效特性?如何优化串行API与ViewModel处理

串行API调用优化与优雅处理方案

问题梳理

你当前的串行API调用存在嵌套回调(回调地狱)、重复错误处理代码、ViewModel中用Any类型接收结果导致代码混乱的问题,需求包括:

  • 简化串行API实现,消除嵌套
  • 统一错误/异常处理,无需重复编写回调
  • ViewModel中优雅处理不同类型的响应结果

解决方案

1. 简化串行调用,消除嵌套回调

利用Kotlin协程的顺序执行特性,结合Sandwich库的API,将嵌套的suspendOnSuccess改为线性调用,同时封装错误处理逻辑减少重复代码。

封装错误处理扩展函数

先封装通用扩展函数,统一处理API响应的错误和异常:

import com.skydoves.sandwich.ApiResponse
import com.skydoves.sandwich.getOrThrow
import com.skydoves.sandwich.onError
import com.skydoves.sandwich.onException

private suspend fun <T> ApiResponse<T>.handleResponse(): T? {
    return this.onError {
        throw ApiErrorException(this.message, this.code)
    }.onException {
        throw this.exception
    }.getOrNull()
}

// 自定义异常类,区分API错误与普通异常
class ApiErrorException(message: String?, val code: Int?) : Exception(message)

重构串行调用代码

用线性调用替代嵌套,每个API步骤依次执行,失败则抛出异常由统一的catch块处理:

suspend fun reCheckout(orderData: OrderData) = flow<ReCheckoutResult> {
    // 1. 登录注册
    val loginResponse = loginAndRegister(orderData.phoneNumber).handleResponse() ?: return@flow
    val jwt = "jwt ${loginResponse.data.auth.token}"
    Client.saveAuth(loginResponse.data)

    // 2. 取消订单
    cancelOrder(
        orderData.orderId,
        OrderCancelRequestBody("store", "re-checkout")
    ).handleResponse() ?: return@flow

    // 3. 添加购物车商品
    addMultiCartItem(jwt, orderData.cartItems).handleResponse() ?: return@flow

    // 4. 结账
    val checkoutResponse = checkOut(orderData.checkoutBody!!).handleResponse() ?: return@flow
    emit(ReCheckoutResult.Success(checkoutResponse.data))
}.catch { e ->
    // 统一处理所有异常
    val result = when (e) {
        is ApiErrorException -> ReCheckoutResult.Error(e.message ?: "请求失败", e.code)
        else -> ReCheckoutResult.Exception(e)
    }
    emit(result)
}

2. 统一错误/异常处理

通过上述扩展函数和flow的catch操作符,实现类似RxJava Single的全局错误/异常处理:

  • 所有API调用的错误和异常都通过handleResponse统一抛出
  • 最终由flow的catch块捕获并转换为统一结果类型,无需在每个API调用后重复编写suspendOnError/suspendOnException

3. ViewModel中优雅处理不同响应类型

使用**密封类(Sealed Class)**定义统一结果类型,替代Any类型,让ViewModel通过类型安全的when表达式处理结果,避免强制类型转换:

定义密封类结果类型

sealed class ReCheckoutResult {
    // 结账成功结果,携带对应数据类型
    data class Success(val checkoutData: CheckoutData) : ReCheckoutResult()
    // API错误结果,携带错误信息和状态码
    data class Error(val errorMessage: String, val errorCode: Int? = null) : ReCheckoutResult()
    // 异常结果,携带Throwable
    data class Exception(val exception: Throwable) : ReCheckoutResult()
}

ViewModel中处理结果

在ViewModel中收集flow时,通过when表达式匹配不同结果类型,代码清晰且类型安全:

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch

class CheckoutViewModel(private val repository: CheckoutRepository) : ViewModel() {

    fun processReCheckout(orderData: OrderData) {
        viewModelScope.launch {
            repository.reCheckout(orderData).collect { result ->
                when (result) {
                    is ReCheckoutResult.Success -> {
                        updateUiWithCheckoutData(result.checkoutData)
                    }
                    is ReCheckoutResult.Error -> {
                        showErrorToast(result.errorMessage)
                    }
                    is ReCheckoutResult.Exception -> {
                        handleNetworkException(result.exception)
                    }
                }
            }
        }
    }

    // 示例处理函数
    private fun updateUiWithCheckoutData(data: CheckoutData) { /*...*/ }
    private fun showErrorToast(message: String) { /*...*/ }
    private fun handleNetworkException(exception: Throwable) { /*...*/ }
}

内容的提问来源于stack exchange,提问作者c-an

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 08:10:21