Kotlin+MVVM框架,最符合实际接口情况、最接地气的封装
大家都已经看过很多MVVM的开发框架了,各式各样的都有,高star的几个项目我也基本都消化一遍,但是都感觉差了点什么。要么封装的太过复杂,别人很难上手,实际也用不上那么复杂的封装;要么就是为了封装而封装,实际情况很难变通;要么就是光顾着搭建架子,实际的restful api接口根本对接不上
1、本框架主要技术关键词:协程suspend、retrofit、smart下拉刷新、BaseRecyclerViewAdapterHelper、ViewBinding、ViewModel
2、本框架优点:非常贴合实际项目需求,用的个别第3方的也都是最前沿的技术。不用sleep,wait模拟服务器接口,本框架直接拿实际网络接口演示
本框架针对下拉刷新、底部加载更多、判断是否有更多页、判断空布局、内存重启时候Fragment处理、、等等问题重点封装,其他无所谓的东西能不封装的就不封装,更方便接入你的项目
ViewModel里监听的接口返回情况,封装的明明白白:
//具体的网络接口返回情况
enumclassLoadState {None,Loading,//下拉刷新开始请求接口 or 普通开始请求接口SuccessHasMore,//下拉刷新请求成功且服务器告诉我还有下一页 or 普通请求成功SuccessNoMore,//下拉刷新请求成功且服务器告诉我已经没有下一页了CodeError,//下拉刷新请求成功但是服务器给我返回了错误的code码 or 普通请求成功但是服务器给我返回了错误的code码NetworkFail,//下拉刷新请求失败 or 普通请求失败,原因是压根就没访问到服务器PageLoading,//底部翻页开始请求接口PageSuccessHasMore,//底部翻页请求成功且服务器告诉我还有下一页PageSuccessNoMore,//底部翻页请求成功且服务器告诉我已经没有下一页了PageCodeError,//底部翻页请求成功但是服务器给我返回了错误的code码PageNetworkFail,//底部翻页请求失败,原因是压根就没访问到服务器}
服务器返回的接口往往是这样的:
"code":1 "message":成功 "data":{ "total":1000 //一共有多少条数据 "totalpage":50 //一共多少页 "currentpage":1 //当前请求的是第几页 "items": [{ //具体的T对象 "name":"小涨" "age":20 } {...} ]}
下面看一下代码:
基于RecyclerView的界面对应的 BaseRVPagerViewModel:
/** * 场景:如果你的列表界面用的是RecyclerView,那么Activity或Fragment里的 MyViewModel 继承这个VM,(T是列表的实体类) * * 特点:不监听list,只监听网络访问状态loadStatus,然后根据不同的loadStatus来直接用list;轻便简单容易理解 * 为什么还有tempList:因为recyclerview有notifyItemRangeInserted,所以翻页的时候要用到这一页的templist,然后用templist做局部刷新*/openclassBaseRVPagerViewModel<T>:ViewModel() {//内部使用可变的Mutableprotectedval_loadStatus=MutableLiveData<LoadState>()//对外开放的是final,这是谷歌官方的写法openval loadStatus:LiveData<LoadState>=_loadStatus//下拉刷新的错误信息,服务器给我返回的 也可以自定义var errorMessage:String?=null//最核心的数据列表,我的做法是:不监听他,直接get他//当然也有人的做法是 LiveData<MutableList<T>> 然后onChange里无脑notityDataChanged,个人觉得那样做反而限制很多//特别注明:如果使用的是BaseRecyclerViewAdapterHelper,他的adapter里有会有个list的指针,我们这里也有个指针,但是内存共用一个openval list:MutableList<T>=arrayListOf()//下拉刷新请求返回的临时templist:var tempRefreshlist:List<T>?=null//翻页请求返回的临时templist://为什么分别定义两个temp:因为极端情况下,下拉刷新和底部翻页同时请求网络,只用一个temp的话就不知道应该setList还是addList//注意:这样做分成两个也不会造成占用内存增加,因为我addList(tempList)之后, 立即templist = nullvar tempPagelist:List<T>?=null//下次请求需要带上的页码参数privatevar page=1/** * 功能:万能的列表请求接口 * @params get请求参数,无需page字段 * @loadmore true = 是底部翻页,false = 下拉刷新 * @block 具体的那两行suspend协程请求网络的代码块,其返回值是网络接口返回值*/openfunrequestList(params:HashMap<String,String>,loadmore:Boolean ,block:suspend()->BasePageEntity<T>){_loadStatus.value= (if (loadmore)LoadState.PageLoadingelseLoadState.Loading)//如果是加载更多,就加上参数page;否则(下拉刷新)就强制设为1,如果服务器要求是0,就改成"0" params["page"]=if (loadmore) page.toString()else"1"//访问网络异常的回调用, 这种方法可以省去try catch, 但不适用于async启动的协程val coroutineExceptionHandler=CoroutineExceptionHandler { coroutineContext, throwable->//这里是主线程; errorMessage="Emm..服务器出小差了";_loadStatus.setValue(if (loadmore)LoadState.PageNetworkFailelseLoadState.NetworkFail ) }/*viewModelScope是一个绑定到当前viewModel的作用域 当ViewModel被清除时会自动取消该作用域,所以不用担心内存泄漏为问题*/ viewModelScope.launch(coroutineExceptionHandler) {//具体的那两行suspend协程请求网络的代码 由VM子类来实现val response:BasePageEntity<T>= block();//如果网络访问异常,代码会直接进入CoroutineExceptionHandler,不会走这里了if (loadmore) {//加载更多if (response.isSuccess) {//加载更多服务器返回成功 page++//这次底部翻页接口返回的具体List<Bean> tempPagelist= response.data?.items//触发activity的onChanged,让activity处理界面_loadStatus.setValue(if (response.data!!.hasMore())LoadState.PageSuccessHasMoreelseLoadState.PageSuccessNoMore )//代码走到这里,tempPagelist已经用完了(把他addAll了),就立即释放掉temp的内存 tempPagelist=null; }else {_loadStatus.setValue(LoadState.PageCodeError) } }else {//下拉刷新请求完毕if (response.isSuccess) { page=2//页面强制设置为下次请求第2页//这次下拉刷新接口返回的具体List<Bean> tempRefreshlist= response.data?.items//触发activity的onChanged,让activity处理界面_loadStatus.setValue(if (response.data!!.hasMore())LoadState.SuccessHasMoreelseLoadState.SuccessNoMore )//代码走到这里,界面已经用过了tempRefreshlist(把他addAll了),就立即释放掉temp的内存 tempRefreshlist=null; }else {//服务器告诉我参数错误_loadStatus.setValue(LoadState.CodeError) errorMessage= response.message } } } }}
以上代码是BaseRVPagerViewModel,其中T是列表的每一行的具体实体类;下面代码是列表界面Activity需要继承自 BaseRVActivity:
/** * 场景:如果Activity里有RecyclerView,那么就继承BaseRVActivity,T是列表数据的每条的Bean,VM 是BaseRVPagerViewModel子类*/openabstractclassBaseRVActivity<T ,VM:BaseRVPagerViewModel<T>> :BaseAppCompatActivity() {protectedval viewModel:VM by lazy {ViewModelProvider(this).get(onBindViewModel()) }overridefuninitView() {super.initView() initRVObservable() }//子类自己写获取adapter的方法(比如new ) 然后通过这个方法返回就行了//out 就是java里的<? extends BaseViewHolder> 就是可以兼容BaseViewHolder的子类abstractfunadapter():BaseQuickAdapter<T,outBaseViewHolder>//子类自己写获取refreshLayout的方法(比如findViewById或者binding.) 然后通过这个方法返回就行了abstractfunrefreshLayout():SmartRefreshLayout//子类重写abstractfunonBindViewModel():Class<VM>protectedopenfuninitRVObservable() {//监听网络返回值 viewModel.loadStatus .observe(this,Observer<Any> { loadState->when (loadState) {LoadState.None-> { }LoadState.Loading-> { }LoadState.SuccessNoMore,LoadState.SuccessHasMore-> { refreshLayout().finishRefresh(0) adapter().setList(viewModel.tempRefreshlist!!)if (loadState===LoadState.SuccessHasMore) refreshLayout().finishLoadMore()else refreshLayout().finishLoadMoreWithNoMoreData()if (viewModel.list.isNullOrEmpty()) { emptyLayout.findViewById<TextView>(R.id.empty_tv).setText("空空如也~") adapter().setEmptyView(emptyLayout) } }LoadState.CodeError,LoadState.NetworkFail-> { refreshLayout().finishRefresh(0) refreshLayout().finishLoadMoreWithNoMoreData()if (viewModel.list.isNullOrEmpty()) { emptyLayout.findViewById<TextView>(R.id.empty_tv).setText(viewModel.errorMessage) adapter().setEmptyView(emptyLayout) } }LoadState.PageLoading-> { }LoadState.PageSuccessHasMore ,LoadState.PageSuccessNoMore-> { adapter().addData(viewModel.tempPagelist!!)if (loadState===LoadState.PageSuccessHasMore) refreshLayout().finishLoadMore()else refreshLayout().finishLoadMoreWithNoMoreData() }LoadState.PageCodeError,LoadState.PageNetworkFail-> refreshLayout().finishLoadMoreWithNoMoreData() } }) }//空布局privateval emptyLayout:View by lazy {LayoutInflater.from(this).inflate(R.layout.listview_empty,null) }}
以上是BaseRVActivity,下面就是具体的Activity的实现方式,我想了很久,到底Adapter实体类 和 ViewModel实体类 和 RefreshLayout实体类 到底是放到BaseRVActivity类里合适,还是放到具体的子类Activity里,最后决定是:ViewModel实体类 放在Base里,因为毕竟是要封装框架,ViewModel是框架级的东西,Base里经常会用到他;而RefreshLayout 和 Adapter 放到具体的子类Activity,因为他们往往会因为界面的个性化,做出具体的调整
以下是具体的子类 UserListActivity 实现方式
/** * RecyclerView的Demo,具体每一条的bean是UserBaseBean,VM是UserArrayViewModel*/classUserListActivity :BaseRVActivity<UserBaseBean,UserListActivity.UserArrayViewModel>() {privatelateinitvar adapter:UserQuickAdapterprivatelateinitvar binding:ActivityRecycleviewBindingoverridefuninitView() {super.initView() adapter=UserQuickAdapter(viewModel.list) binding.recyclerView.layoutManager=LinearLayoutManager(this) binding.recyclerView.adapter= adapter }overridefunonCreate(savedInstanceState:Bundle?) {super.onCreate(savedInstanceState) binding=DataBindingUtil.setContentView(this,R.layout.activity_recyclerview) initView(); binding.refreshLayout.setOnRefreshListener {val params=HashMap<String,String>() params["keyword"]="小" viewModel.requestUserList(params,false) } binding.refreshLayout.setOnLoadMoreListener {val params=HashMap<String,String>() params["keyword"]="小" viewModel.requestUserList(params,true) }//demo 添加的 Header//Header 是自行添加进去的 View,所以 Adapter 不管理 Header 的 DataBinding。//请在外部自行完成数据的绑定// val view: View = layoutInflater.inflate(R.layout.listitem_follower, null, false)// view.findViewById(R.id.iv).setVisibility(View.GONE)// adapter.addHeaderView(view) binding.refreshLayout.autoRefresh(100,200,1f,false);//延迟100毫秒后自动刷新//item 点击事件// adapter.setOnItemClickListener(object : OnItemClickListener() {// fun onItemClick(adapter: BaseQuickAdapter<*, *>?, view: View?, position: Int) {// }// }) }overridefungetTootBarTitle():String {return"RecyclerView列表" }//本界面对应的VM类,如果VM复杂的话,也可以独立成一个外部文件classUserArrayViewModel:BaseRVPagerViewModel<UserBaseBean>() {//按MVVM设计原则,请求网络应该放到更下一层的"仓库类"里,但是我感觉如果你只做网络不做本地取数据,没必要//请求用户列表接口funrequestUserList(params:HashMap<String,String> ,loadmore:Boolean){//调用"万能列表接口封装"super.requestList(params, loadmore){//用kotlin高阶函数,传入本Activity的"请求用户列表接口的代码块" 就是这3行代码var apiService:UserApiService=RetrofitInstance.instance.create(UserApiService::class.java)val response:BasePageEntity<UserBaseBean>= apiService.userList(params) response } } }overridefunadapter():UserQuickAdapter= adapteroverridefunrefreshLayout():SmartRefreshLayout= binding.refreshLayoutoverridefunonBindViewModel():Class<UserArrayViewModel>=UserArrayViewModel::class.java}
此外,本框架还做了对网络请求的封装,这个并不是本框架最大亮点,就不再贴代码了