结合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
相关产品推荐
相关产品推荐

