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

结合Kotlin Flow与Retrofit实现UseCases时如何减少样板代码?

协程+MVVM Clean架构下UseCase层的优化方案

1. 彻底替换Flow操作符嵌套,改用flow构建块内顺序调用挂起函数

你已经把Repository层改为普通挂起函数,完全不需要再用flatMapMerge这类Rx残留风格的Flow操作符处理串行逻辑,直接在flow { }构建块内按顺序调用挂起函数即可,彻底消除嵌套样板。
示例对比:

// 旧写法:嵌套flatMapMerge的冗余代码
fun oldSerialRequestUseCase(param: Param): Flow<Result> {
    return repo.firstRequest(param)
        .flatMapMerge { firstRes ->
            repo.secondRequest(firstRes.id)
        }
        .flatMapMerge { secondRes ->
            repo.thirdRequest(secondRes.token)
        }
        .catch { emit(handleError(it)) } // catch位置不对很容易漏异常
}

// 新写法:顺序调用无嵌套
fun newSerialRequestUseCase(param: Param): Flow<Result> = flow {
    val firstRes = repo.firstRequest(param)
    val secondRes = repo.secondRequest(firstRes.id)
    val thirdRes = repo.thirdRequest(secondRes.token)
    emit(Result.Success(thirdRes))
}.catch { emit(Result.Error(it)) } // 顶层catch统一捕获整个流程所有异常

2. 异常处理优化

之前用catch算子报错大概率是因为作用域问题,采用上述结构后,你可以根据需求灵活选择异常处理粒度:

  • 统一处理全流程异常:直接在flow构建块后接catch算子,可捕获流程内所有挂起函数抛出的异常
  • 单接口单独处理异常:在单个挂起函数外包try-catch,可针对特定接口的异常做定制化降级逻辑,不会中断整个串行流程
  • 建议提前封装密封类作为统一返回状态,上层ViewModel无需处理原始异常,直接消费状态即可:
sealed interface RequestResult<out T> {
    data class Success<out T>(val data: T) : RequestResult<T>
    data class Error(val throwable: Throwable, val msg: String = throwable.message.orEmpty()) : RequestResult<Nothing>
    object Loading : RequestResult<Nothing>
}

3. 抽取公共UseCase基类消除样板

可以把Flow构建、异常处理、Loading状态发送的通用逻辑抽到基类,子类仅需实现核心业务逻辑:

abstract class BaseUseCase<in P, out R> {
    operator fun invoke(param: P): Flow<RequestResult<R>> = flow {
        emit(RequestResult.Loading)
        val result = execute(param)
        emit(RequestResult.Success(result))
    }.catch { emit(RequestResult.Error(it)) }

    protected abstract suspend fun execute(param: P): R
}

// 子类仅需实现核心业务逻辑,无任何样板
class SerialRequestUseCase(private val repo: Repo) : BaseUseCase<Param, ThirdRes>() {
    override suspend fun execute(param: Param): ThirdRes {
        val firstRes = repo.firstRequest(param)
        val secondRes = repo.secondRequest(firstRes.id)
        return repo.thirdRequest(secondRes.token)
    }
}

4. 灵活支持并发需求

如果后续需要部分接口并行调用,直接用coroutineScope + async即可,代码逻辑清晰无需学习复杂的Flow组合操作符:

override suspend fun execute(param: Param): CombineRes = coroutineScope {
    val userDeferred = async { repo.getUserInfo(param.userId) }
    val configDeferred = async { repo.getAppConfig() }
    // 两个请求并行执行,等待全部返回后组合结果
    CombineRes(userDeferred.await(), configDeferred.await())
}

5. ViewModel层调用优化

你可以直接在ViewModel中用launchWhenStarted/repeatOnLifecycle收集UseCase返回的Flow,自动绑定生命周期避免内存泄漏,无需手动处理协程取消。

内容的提问来源于stack exchange,提问作者4gus71n

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.23 17:54:05