我是如何使用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.

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

相关推荐
带电的小王2 小时前
WhisperKit: Android 端测试 Whisper -- Android手机(Qualcomm GPU)部署音频大模型
android·智能手机·whisper·qualcomm
梦想平凡2 小时前
PHP 微信棋牌开发全解析:高级教程
android·数据库·oracle
元争栈道2 小时前
webview和H5来实现的android短视频(短剧)音视频播放依赖控件
android·音视频
阿甘知识库3 小时前
宝塔面板跨服务器数据同步教程:双机备份零停机
android·运维·服务器·备份·同步·宝塔面板·建站
元争栈道4 小时前
webview+H5来实现的android短视频(短剧)音视频播放依赖控件资源
android·音视频
MuYe4 小时前
Android Hook - 动态加载so库
android
居居飒4 小时前
Android学习(四)-Kotlin编程语言-for循环
android·学习·kotlin
Henry_He7 小时前
桌面列表小部件不能点击的问题分析
android
工程师老罗8 小时前
Android笔试面试题AI答之Android基础(1)
android
qq_397562319 小时前
android studio更改应用图片,和应用名字。
android·ide·android studio