各位好,本人是练习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.
由于本人能力有限,不足之处还望大佬指正.