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

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

相关推荐
sweetying3 小时前
30了,人生按部就班
android·程序员
用户2018792831673 小时前
Binder驱动缓冲区的工作机制答疑
android
真夜3 小时前
关于rngh手势与Slider组件手势与事件冲突解决问题记录
android·javascript·app
用户2018792831673 小时前
浅析Binder通信的三种调用方式
android
用户094 小时前
深入了解 Android 16KB内存页面
android·kotlin
火车叼位4 小时前
Android Studio与命令行Gradle表现不一致问题分析
android
前行的小黑炭6 小时前
【Android】 Context使用不当,存在内存泄漏,语言不生效等等
android·kotlin·app
前行的小黑炭7 小时前
【Android】CoordinatorLayout详解;实现一个交互动画的效果(上滑隐藏,下滑出现);附例子
android·kotlin·app
用户20187928316719 小时前
Android黑夜白天模式切换原理分析
android
芦半山20 小时前
「幽灵调用」背后的真相:一个隐藏多年的Android原生Bug
android