安卓MVVM开发:基于AAC架构玩安卓客户端(Databinding+LiveData+ViewModel+Coroutines+Repository),(上)

一、开篇

全篇内容将简单介绍AAC的在我个人开发上的应用, AAC即Android Architecture Components,一个处理UI的生命周期与数据的持久化的架构。

核心使用:LiveData、ViewModel、Databinding 、Lifecycles

辅助:Room、WorkManager 、Glide等等常用框架

全篇大部分内容都围绕Kotlin,这门Google强推的语言上,加上协程这个工具的出现,使得Kotlin对于网络请求,异步操作变得更加方便。所以网络部分我已经不再使用Retrofit2 + Rxjava2了。

首先、本客户端借助玩安卓的API实现网络功能,非常感谢鸿洋大神玩安卓,给我们提供了分享知识和技术的地方

玩安卓:https://www.wanandroid.com/

接着本客户端使用了来自github的第三方库为:

1、live-event-bus github地址:https://github.com/JeremyLiao/LiveEventBus 一款很优秀的消息总线。

2、Activity返回侧滑动画SlideBack github地址:https://github.com/ParfoisMeng/SlideBack 项目中下载了源码并进行了部分修改。

非常感谢所有作者

二、玩安卓部分预览图

image
image

三、图片墙部分预览图:

本部分采用Room实现基本登录注册,点赞,下单(假功能,仅效果显示)的操作

image

四、从网络开始讲起

在使用的API方法上加上suspend

    @FormUrlEncoded

    @POST("user/login")

    suspend fun login(@Field("username") username:String,@Field("password") password:String) : Response<JsonResult<Auth>>

处理请求的逻辑封装了另一个类CallResult,只显示关键部分

   fun hold(result: suspend () -> Response<JsonResult<T>>): CallResult<T> {
       var response: Response<JsonResult<T>>?
       var netJob: Job? = null
       owner?.apply {
           netJob = lifecycleScope.launchWhenStarted {
               __________ 处理loading状态 ————————————————
               response  = withContext(Dispatchers.IO) {
                   withTimeoutOrNull(10000){//超时处理
                       result.invoke() //网络请求
                   }
               }
               if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
                   withContext(Dispatchers.Main) {
                       __________ 处理超时和返回结果的逻辑————————————————
                   }
               } else {
                   netJob?.cancel()
               }
           }
       }
       return this
   }

使用时候如下:

  fun login(username:String,password:String,call:MutableLiveData<Result<Auth>>){
      CallResult<Auth>(owner)
          .loading {
               __________ 处理读取————————————————
          }.success { result, message ->
              __________ 处理成功————————————————
              call.value = result
          }.error { result, code, message ->
               __________ 处理错误————————————————
              call.value = result
          }.outTime {
                __________ 处理超时————————————————
              call.value = it
          }.hold {
              api.login(username, password)//登录
          }
  }

五、架构细讲

由于着重在AAC上,所以采用了MVVM的设计模式,利用DataBinding的优势,把数据和交互的简单操作,都能在xml上完成
以下拉加载和读取更多为例子

xml如下

<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
                android:id="@+id/refresh"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
                onRefresh="@{Home::loadRefresh}"
                refreshing="@{Home.refreshing}"
                >


                <showmethe.github.core.widget.common.AutoRecyclerView
                    android:id="@+id/rv"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:requiresFadingEdge="vertical"
                    android:fadingEdge="vertical"
                    android:fadingEdgeLength="@dimen/px20dp"
                    loadMore="@{Home::loadMore}"
                    />

            </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

请勿直接复制,省略databinding的set操作和初始化

class HomeFragment : LazyFragment<FragmentHomeBinding, MainViewModel>() {
     val refreshing = MutableLiveData<Boolean>()   //不能private
      private val pagerNumber = MutableLiveData<Int>()

override fun observerUI() {

 pagerNumber.observe(this, Observer {
            it?.apply {
                router.toTarget("getHomeArticle",this) //这个待会讲,利用注解调用viewModel中的方法
            }
        })
}
       //___________省略无关代码_________
    fun loadMore(){//不能private
        pagerNumber.value =  pagerNumber.value!! + 1
    }
    fun loadRefresh(){
        pagerNumber.value = 0
    }
  }
}

对应的BindingAdapter拓展方法,利用了Kotlint的特性

@BindingAdapter("loadMore")
fun AutoRecyclerView.loadMore(loadingMore: (()->Unit)?){
    setOnLoadMoreListener {
        loadingMore?.invoke()
    }
}

@BindingAdapter("onRefresh")
fun SwipeRefreshLayout.onRefresh(onRefreshListener: SwipeRefreshLayout.OnRefreshListener?){
    setOnRefreshListener(onRefreshListener)
}

@BindingAdapter("refreshing")
fun SwipeRefreshLayout.refreshing(newValue: Boolean) {
    if(isRefreshing != newValue)
    isRefreshing = newValue
}

5.1 VMRouter的使用

在上面代码中有一段router.toTarget("getHomeArticle",this)的代码,是用来调用ViewModel中的方法,ViewModel中使用注解VMPath 和一个在该ViewModel中唯一的路径,如下:

 /**
     * 登录
     */
    @VMPath("login")
    fun login(username:String,password:String){
        repository.login(username, password, auth)
    }

通过反射拿到对应方法,并缓存起来。其实这里是因为存在反射,其实是会有性能的问题,但是这个消耗很低。

5.2 RecyclerViewAdapter的封装

既然使用了Databinding,那不可不提ObservableArrayList,这个类可以让你对数组的变化进行刷新处理,不在需要每次新数据都自己调用Adapter的刷新,减少漏调时候出现的奇怪问题。实现OnListChangedCallback即可,

 /**
     * The callback that is called by ObservableList when the list has changed.
     */
    abstract class OnListChangedCallback<T extends ObservableList> {

        /**
         * Called whenever a change of unknown type has occurred, such as the entire list being
         * set to new values.
         *
         * @param sender The changing list.
         */
        public abstract void onChanged(T sender);

        /**
         * Called whenever one or more items in the list have changed.
         * @param sender The changing list.
         * @param positionStart The starting index that has changed.
         * @param itemCount The number of items that have changed.
         */
        public abstract void onItemRangeChanged(T sender, int positionStart, int itemCount);

        /**
         * Called whenever items have been inserted into the list.
         * @param sender The changing list.
         * @param positionStart The insertion index
         * @param itemCount The number of items that have been inserted
         */
        public abstract void onItemRangeInserted(T sender, int positionStart, int itemCount);

        /**
         * Called whenever items in the list have been moved.
         * @param sender The changing list.
         * @param fromPosition The position from which the items were moved
         * @param toPosition The destination position of the items
         * @param itemCount The number of items moved
         */
        public abstract void onItemRangeMoved(T sender, int fromPosition, int toPosition,
                int itemCount);

        /**
         * Called whenever items in the list have been deleted.
         * @param sender The changing list.
         * @param positionStart The starting index of the deleted items.
         * @param itemCount The number of items removed.
         */
        public abstract void onItemRangeRemoved(T sender, int positionStart, int itemCount);
    }

再结合Databinding的特性,不再需要ButterKnife和findviewbyId找到view中对应的id,而且利用数据绑定,而且数据绑定使得有时候连Id也不再需要书写了,当然有些场景还是需要的,代码更加的简洁和方便了,但是也有人觉得这样不好维护,但我觉得数据绑定减少了不少代码的书写,再加上kotlin更加简洁了。

项目github地址:https://github.com/ShowMeThe/WanAndroid

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341