基于Kotlin+Flow+Retrofit+Jetpack+MVVM架构实现WanAndroid客户端开发搭建

·  阅读 6490
基于Kotlin+Flow+Retrofit+Jetpack+MVVM架构实现WanAndroid客户端开发搭建

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

前言

距离上一次更文已经过去了一个月了,因为这段时间有点忙,公司需要开发一个新的包给主项目导流,经过时不时的加加班,上周终于上线了。这个项目其实在上个月写完最后一篇更文后就开始搭建了,但因为时间不是很充足,过去一个月中也经常零零散散的写着,所以拖到了现在,所以说坚持写博客真的挺难的…做WanAndroid这个项目主要是对过去几个月KotlinJetpack系列更文学习的总结,这个项目的功能并未全部都做了,因为我们的目的不是再造一个WanAndroid客户端,只是学习搭建和使用Kotlin+MVVM这一种架构。当然,项目只有功能未完全,但是其他配置,如混淆、多渠道打包都实现了的。

项目简介

项目是采用Kotlin编写,采用MVVM架构,结合ViewMdel、Lifecycle、paging、LiveData、navigation等Jetpack组件以及Retrofit使用。API来自鸿神的WanAndroid ,项目的大部分资源文件及部分代码来自WanAndroid站内的开源WanAndroid项目,UI效果也是参照这个项目来实现的。

项目效果

AiVideo_20220617175113.gif

项目Github地址:github.com/Jeremyzwc/W…

封装介绍

基类封装

BaseActivity对ViewBinding封装

BaseActivity主要是对ViewBinding的封装:

abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity(){
复制代码

核心代码主要是对ViewBinding的处理:

private val _viewBinding: VB by lazy {
    val type = javaClass.genericSuperclass
    val vbClass: Class<VB> = type!!.saveAs<ParameterizedType>().actualTypeArguments[0].saveAs()
    val method = vbClass.getDeclaredMethod("inflate", LayoutInflater::class.java)
    method.invoke(this, layoutInflater)!!.saveAsUnChecked()
}
复制代码
BaseVMActivity对ViewModel封装

核心代码:

abstract class BaseVMActivity<VB : ViewBinding, VM : BaseViewModel> : BaseActivity<VB>() {

    protected val viewModel: VM by lazy {
        val type = javaClass.genericSuperclass
        val vmClass: Class<VM> = type!!.saveAs<ParameterizedType>().actualTypeArguments[1].saveAs()
        ViewModelProvider(this, ViewModelProvider.NewInstanceFactory()).get(vmClass)
    }
}
复制代码

BaseVMActivity中的initCommObserver方法主要是处理一些公共事件:

protected fun initCommObserver() {

    viewModel.dialogLoadingEvent.observe(this) {
        if (it.loadingState) {
            if (TextUtils.isEmpty(it.loadingMsg)) showDialogloading() else showDialogloading(it.loadingMsg)
        } else {
            loadingDialog.dismiss()
        }
    }

    viewModel.layoutLoadingEvent.observe(this) {
        val rlLoading = viewBinding?.root?.findViewById<RelativeLayout>(R.id.rl_loading)
        rlLoading?.visibility = if(it) View.VISIBLE else View.GONE
    }

    viewModel.loadErrorEvent.observe(this) {
        isShowErrorLayout = it.loadingErrorState
        errorMsg = it.loadingErrorMsg
        val llError = viewBinding?.root?.findViewById<LinearLayout>(R.id.ll_error)
        val tvError = viewBinding?.root?.findViewById<TextView>(R.id.tv_error)
        llError?.visibility = if(it.loadingErrorState) View.VISIBLE else View.GONE
        tvError?.text = it.loadingErrorMsg
    }

    viewModel.requestErrorEvent.observe(this) {
        ToastUtils.show(it)
    }
}
复制代码

BaseFragment和BaseVMFragment的作用和Base的activity基本一致

BaseViewModel

BaseViewMode主要是处理一些公共View的逻辑,实现ViewModel

BaseLifecycleDialog

BaseLifecycleDialog封装主要实现了ViewBindingDefaultLifecycleObserverDefaultLifecycleObserver实现和生命周期绑定,对于内存泄漏可以放心了。

BasePagingSource

BasePagingSource主要封装了paging的分页加载处理,不用每一个实现的PagingSource都要去处理page的逻辑。

BasePagingDataAdapter

PagingDataAdapter的封装。

如果觉得paging难用,BaseRecyclerViewAdapterHelper也是很香的。

BaseRvAdapter

RecyclerView.Adapter的封装,在不使用分页的列表时使用。

Room数据库封装实现浏览记录功能:WanDB

@Database(entities = [ScanRecordEnity::class], version = 1, exportSchema = false)
abstract class WanDB : RoomDatabase(){

    abstract fun getScanRecordDao(): ScanRecordDao

    companion object {
        @Volatile
        private var instantce: WanDB? = null
        private const val DB_NAME = "wan_android.db"

        fun getInstance(context: Context): WanDB? {
            if (instantce == null) {
                synchronized(WanDB::class.java) {
                    if (instantce == null) {
                        instantce = createInstance(context)
                    }
                }
            }
            return instantce
        }

        private fun createInstance(context: Context): WanDB {
            return Room.databaseBuilder(context.applicationContext, WanDB::class.java, DB_NAME)
                .allowMainThreadQueries()
                .build()
        }
    }
}
复制代码

http请求封装

对于网络请求封装这一块,应该是花的时间是最多的,主要是一开始是根据官网那样做,加一层Repository去管理网络请求调用,后面参考关于Flow封装网络库的这篇文章juejin.cn/post/696355… ,觉得非常的方便,对比官网的做法是更加简洁的,所以我就基本用他的这种方法来修改。核心代码在FlowVmKtx.kt文件,如下:

suspend fun <T> BaseViewModel.launchFlow(showLayoutLoading: Boolean = true, isToastError :Boolean = true, request: suspend WanAndroidApiService.() -> BaseResponse<T>): Flow<BaseResponse<T>> {

    if (showLayoutLoading) {
        showLayoutLoading()
    }
    return flow {
        val response = request.invoke(apiService)
        if (!response.isSuccess) {
            throw ApiException(response.errorMsg ?: "", response.errorCode!!)
        }
        emit(response)
    }.flowOn(Dispatchers.IO)
        .onCompletion { throwable ->
            if(showLayoutLoading){
                hideLayoutLoading()
            }
            throwable?.let { throw catchException(this@launchFlow, throwable,isToastError) }
        }
}


suspend fun <T> BaseViewModel.postFlow(showDialogLoading: Boolean = true, isToastError :Boolean = true,loadingStr: String = "加载中...", request: suspend WanAndroidApiService.() -> BaseResponse<T>): Flow<BaseResponse<T>> {

    if (showDialogLoading) {
        showDialogLoading(DialogLoadingEvent(loadingStr, true))
    }
    return flow {
        val response = request.invoke(apiService)
        if (!response.isSuccess) {
            throw ApiException(response.errorMsg ?: "", response.errorCode!!)
        }
        emit(response)
    }.flowOn(Dispatchers.IO)
        .onCompletion { throwable ->
            if (showDialogLoading) {
                cloaseDialogLoading(DialogLoadingEvent("", false))
            }
            throwable?.let { throw catchException(this@postFlow, throwable,isToastError) }
        }
}

复制代码

这里我分了两个方法,一种是加载数据的情况,另外一种是如登陆这种接口,是往后代post的动作,体现两种加载的动画,也可以写成一个方法,这里我是想区分开来。

在ViewModel中使用:

fun getBanner(){
    viewModelScope.launch {
        launchFlow(false) {
            getBanners()
        }.next {
            bannerLiveData.postValue(data)
        }
    }
}
复制代码

关于网络请求库这一块,建议可以看看RxHttp,它也是可以支持协程的,让我们更易于封装,在我们公司的项目中也在使用它,作者一直在维护更新,这个项目也让我第一次donattion。因为这里我们做学习分享,所以是基于Retrofit来做封装。

总结

Github下载

特别感谢

感谢鸿神WanAndroid网站提供的开放API

参考资料:

github.com/iceCola7/Wa… juejin.cn/post/696355…

分类:
Android
收藏成功!
已添加到「」, 点击更改