我是如何使用Flow+Retrofit封装网络请求的

各位好,本人是练习kt时长一年的准练习生,接下来我将用一种我认为还行的方式封装网络请求,如有雷点,请各位佬轻喷 首先,定义一个请求结果类

kotlin 复制代码
sealed class RequestResult<out T> {
    data object INIT : RequestResult<Nothing>()
    data object LOADING : RequestResult<Nothing>()
    data class Success<out T>(val data: T) : RequestResult<T>()
    data class Error(val errorCode: Int = -1, val errorMsg: String? = "") : RequestResult<Nothing>()
}

接下来,定义Retrofit的service,由于我个人的极简主义,特别讨厌复制粘贴,所以我做了一个非常大胆的决定

less 复制代码
interface SimpleService {
    //目前我们只关注这两方法
    @GET
    suspend fun commonGet(@Url url: String, @QueryMap param: HashMap<String, Any>): ApiResponse<Any>
    //目前我们只关注这两方法
    @POST
    suspend fun commonPost(@Url url: String, @Body param: HashMap<String, Any>): ApiResponse<Any>

    @GET
    suspend fun commonGetList(@Url url: String, @QueryMap param: HashMap<String, Any>): ApiListData<Any>

    @POST
    suspend fun commonPostList(@Url url: String, @Body param: HashMap<String, Any>): ApiListData<Any>

    @GET
    suspend fun commonGetPageList(@Url url: String, @QueryMap param: HashMap<String, Any>): ApiPageData<Any>

    @POST
    suspend fun commonPostPageList(@Url url: String, @Body param: HashMap<String, Any>): ApiPageData<Any>
}

and在apiManager中生成这个service

kotlin 复制代码
object BaseApiManager {
val simpleService by lazy<SimpleService> {
    getService()
}

接下来我定义了一个RequestParam类来帮助收敛请求需要的参数

kotlin 复制代码
@Keep
data class RequestParam<T>(
    val clazz: Class<T>? = null,
    val url: String,
    val isGet: Boolean = true,
    val paramBuilder: (HashMap<String, Any>.() -> Unit)? = null
){
    val param: HashMap<String, Any>
        get() {
            val value = hashMapOf<String, Any>()
            paramBuilder?.invoke(value)
            return value
        }
}

再然后便是请求真正发出的地方

kotlin 复制代码
internal fun <T> commonRequest(
    param: RequestParam<T>,
    builder: ((T) -> Unit)? = null
) = flow {
    emit(RequestResult.LOADING)
    Timber.d(param.param.toString())
    runCatching {
        if (param.isGet) {
            BaseApiManager.simpleService.commonGet(param.url, param.param)
        } else {
            BaseApiManager.simpleService.commonPost(param.url, param.param)
        }
    }.onSuccess {
        if (it.code != StatusCode.REQUEST_SUCCESS) {
            emit(RequestResult.Error(it.code, it.message))
        } else {
            val gson = Gson()
            val data = gson.fromJson(gson.toJson(it.data), param.clazz)
            builder?.invoke(data)
            emit(RequestResult.Success(data))
        }
    }.onFailure {
        emit(RequestResult.Error(StatusCode.REQUEST_FAILED, it.message))
    }
}.flowOn(Dispatchers.IO)

在经过上述封装后,此时我在vm中发出一个网络请求就变成

rust 复制代码
viewModelScope.launch {
    commonRequest(
        RequestParam(
            XXXBean::class.java, //数据类class
            "/xxx/xxx/xxx", //地址
            false //是否get
        ) {
            put("xxx", 11)
            put("xxxx", "25")
        }
    ).collect {
        when(it) {
            RequestResult.INIT -> {
                假设这边是Init弹窗
            }
            RequestResult.LOADING -> {
                关闭Init弹窗
                假设这边是Loading弹窗
            }
            is RequestResult.Error -> {
                关闭Loading弹窗
                toast(it.errorMsg)
            }

            is RequestResult.Success -> {
               关闭Loading弹窗
               发送成功事件或者改变UI状态
            }
        }
    }

那么这边会遇到一个有点烦人的事情,实际上

rust 复制代码
RequestResult.INIT -> {
    假设这边是Init弹窗
}
RequestResult.LOADING -> {
    关闭Init弹窗
    假设这边是Loading弹窗
}
is RequestResult.Error -> {
    关闭Loading弹窗
    toast(it.errorMsg)
}

这三兄弟中,我们经常会做一些重复的操作,于是我略施小计,将这几个行为定义成CommonEffect

kotlin 复制代码
sealed class MVICommonEffect {
    data object ShowLoading: MVICommonEffect()
    data object DismissLoading: MVICommonEffect()
    data class ShowToast(val msg: String?): MVICommonEffect()
}

同时将Flow<RequestResult>的订阅步骤拆开,由于kt中两个隐式this对象写起来很繁琐,所以我是把这一串代码放到baseiewModel中的

kotlin 复制代码
fun <T> Flow<RequestResult<T>>.onInit(initBlock: suspend () -> Unit): Flow<RequestResult<T>> {
    return onEach {
        if (it == RequestResult.INIT) {
            initBlock.invoke()
        }
    }
}

fun <T> Flow<RequestResult<T>>.onLoading(
    showLoading: Boolean = true,
    loadingBlock: suspend () -> Unit
): Flow<RequestResult<T>> {
    return onEach {
        if (it == RequestResult.LOADING) {
            if (showLoading) {
                emitLoadingEffect()
            }
            loadingBlock.invoke()
        }
    }
}

fun <T> Flow<RequestResult<T>>.onSuccess(
    dismissLoading: Boolean = true,
    successBlock: suspend ((data: T) -> Unit)
): Flow<RequestResult<T>> {
    return onEach {
        if (it is RequestResult.Success) {
            if (dismissLoading) {
                emitDismissLoadingEffect()
            }
            successBlock.invoke(it.data)
        }
    }
}

fun <T> Flow<RequestResult<T>>.onError(
    dismissLoading: Boolean = true,
    showToast: Boolean = true,
    errorBlock: suspend (code: Int, msg: String?) -> Unit
): Flow<RequestResult<T>> {
    return onEach {
        if (it is RequestResult.Error) {
            if (dismissLoading) {
                emitDismissLoadingEffect()
            }
            if (showToast) {
                emitToastEffect(it.errorMsg)
            }
            errorBlock.invoke(it.errorCode, it.errorMsg)
        }
    }
}

fun <T> Flow<RequestResult<T>>.onCommonSuccess(
    loadingInvoke: Boolean,
    showToast: Boolean,
    successBlock: suspend ((data: T) -> Unit)
) = this.onInit().onLoading(loadingInvoke)
    .onError(
        dismissLoading = loadingInvoke,
        showToast = showToast
    ).onSuccess(
        dismissLoading = loadingInvoke
    ) {
        successBlock.invoke(it)
    }
    
private val _commonEffect = MutableSharedFlow<MVICommonEffect>()
override val commonEffect: SharedFlow<MVICommonEffect> by lazy {
    _commonEffect.asSharedFlow()
}

override suspend fun emitLoadingEffect() {
    _commonEffect.emit(MVICommonEffect.ShowLoading)
}

override suspend fun emitDismissLoadingEffect() {
    _commonEffect.emit(MVICommonEffect.DismissLoading)
}

override suspend fun emitToastEffect(msg: String?) {
    _commonEffect.emit(MVICommonEffect.ShowToast(msg))
}

那么接下来,vm中网络请求就可以用一种很赏心悦目的方式出现了

kotlin 复制代码
private fun requestTestData(): Flow<RequestResult<XXXBean>> {
    return commonRequest(
        RequestParam(
            XXXBean::class.java,
            "xxx"
        )
    )
}
    
private fun updateTestData() {
    requestData().onInit().onLoading().onError().onSuccess {
        Timber.d(it.toString)
    }.launchIn(viewModelScope)
}

接下来,只需要在基类View中订阅上述的MVICommonEffect,就可以handle大部分情况下的loading,toast.

由于本人能力有限,不足之处还望大佬指正.

相关推荐
loitawu23 分钟前
Rockchip Android16 系统裁剪指南
android·android16·android裁剪·系统裁剪·rockchip app
小羊子说1 小时前
关于车机中的升级流程小结(SOC、MCU、4G升级流程)
android·adb·性能优化·车载系统
肖。35487870942 小时前
[技巧-11]AndroidManifest.xml完善小技巧。
android
小羊子说2 小时前
Android 车机开发中常用的adb 脚本(更新中)
android·linux·adb·性能优化·车载系统
用户7607495397832 小时前
Android页面四大布局运行结果
android
风往哪边走2 小时前
搜索框自定义
android
用户8249281925363 小时前
把android资源类型详解
android
IT观测3 小时前
深度分析俩款主流移动统计工具Appvue和openinstall
android·java·数据库
用户338675581953 小时前
Android 四种常用布局完全解析(附实战项目截图)
android
用户5087532168443 小时前
Android 资源类型全解析:深入理解四种常用布局
android