Android Jetpack系列之MVVM使用及封裝

Android開發(fā)架構(gòu)

如果開發(fā)過程中大家各自為戰(zhàn)层坠,沒有統(tǒng)一規(guī)范,久而久之买猖,項(xiàng)目代碼會(huì)變得混亂且后續(xù)難以維護(hù)改橘。當(dāng)使用統(tǒng)一的架構(gòu)模式后,有很多的好處玉控,如:

  • 統(tǒng)一開發(fā)規(guī)范飞主,使得代碼整潔、規(guī)范高诺,后續(xù)易于維護(hù)及擴(kuò)展
  • 提高開發(fā)效率(尤其在團(tuán)隊(duì)人員較多時(shí))
  • 模塊單一職責(zé)碌识,使得模塊專注自己內(nèi)部(面向?qū)ο?,模塊間解耦

總之懒叛,開發(fā)架構(gòu)是前人總結(jié)出來(lái)的一套行之有效的開發(fā)模式丸冕,目的是達(dá)到高內(nèi)聚耽梅,低耦合的效果薛窥,使得項(xiàng)目代碼更健壯、易維護(hù)眼姐。

Android中常見的架構(gòu)模式有MVC(Model-View-Controller)诅迷、MVP(Model-View-Presenter)MVVM(Model-View-ViewModel)众旗,一起來(lái)看下各自的特點(diǎn):

MVC

MVC(Model-View-Controller)是比較早期的架構(gòu)模式罢杉,模式整體也比較簡(jiǎn)單。

mvc.png

MVC模式將程序分成了三個(gè)部分:

  • Model模型層:業(yè)務(wù)相關(guān)的數(shù)據(jù)(如網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)贡歧、本地?cái)?shù)據(jù)庫(kù)數(shù)據(jù)等)及其對(duì)數(shù)據(jù)的處理
  • View視圖層:頁(yè)面視圖(通過XML布局編寫視圖層)滩租,負(fù)責(zé)接收用戶輸入、發(fā)起數(shù)據(jù)請(qǐng)求及展示結(jié)果頁(yè)面
  • Controller控制器層:M與V之間的橋梁利朵,負(fù)責(zé)業(yè)務(wù)邏輯

MVC特點(diǎn):

  • 簡(jiǎn)單易用:上圖表述了數(shù)據(jù)整個(gè)流程:View接收用戶操作律想,通過Controller去處理業(yè)務(wù)邏輯,并通過Model去獲取/更新數(shù)據(jù)绍弟,然后Model層又將最新的數(shù)據(jù)傳回View層進(jìn)行頁(yè)面展示技即。
  • 架構(gòu)簡(jiǎn)單的另一面往往是對(duì)應(yīng)的副作用:由于XML布局能力弱,我們的View層的很多操作都是寫在Activity/Fragment中樟遣,同時(shí)而叼,Controller身笤、Model層的代碼也大都寫在Activity/Fragment中,這就會(huì)導(dǎo)致一個(gè)問題葵陵,當(dāng)業(yè)務(wù)邏輯比較復(fù)雜時(shí)液荸,Activity/Fragment中的代碼量會(huì)很大,其違背了類單一職責(zé)脱篙,不利于后續(xù)擴(kuò)展及維護(hù)莹弊。尤其是后期你剛接手的項(xiàng)目,一個(gè)Activity/Fragment類中的代碼動(dòng)輒上千行代碼涡尘,那感覺著實(shí)酸爽:
beng

當(dāng)然忍弛,如果業(yè)務(wù)很簡(jiǎn)單,使用MVC模式還是一種不錯(cuò)的選擇考抄。

MVP

MVP(Model-View-Presenter)细疚,架構(gòu)圖如下:

mvp.png

MVP各模塊職責(zé)如下

  • Model模型:業(yè)務(wù)相關(guān)的數(shù)據(jù)(如網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)、本地?cái)?shù)據(jù)庫(kù)數(shù)據(jù)等)及其對(duì)數(shù)據(jù)的處理
  • View視圖:頁(yè)面視圖(Activity/Fragment)川梅,負(fù)責(zé)接收用戶輸入疯兼、發(fā)起數(shù)據(jù)請(qǐng)求及展示結(jié)果頁(yè)面
  • Presenter:M與V之間的橋梁,負(fù)責(zé)業(yè)務(wù)邏輯

MVP特點(diǎn)View層接收用戶操作贫途,并通過持有的Presenter去處理業(yè)務(wù)邏輯吧彪,請(qǐng)求數(shù)據(jù);接著Presenter層通過Model去獲取數(shù)據(jù)丢早,然后Model又將最新的數(shù)據(jù)傳回Presenter層姨裸,Presenter層又持有View層的引用,進(jìn)而將數(shù)據(jù)傳給View層進(jìn)行展示怨酝。

MVP相比MVC的幾處變化

  • View層與Model層不再交互傀缩,而是通過Presenter去進(jìn)行聯(lián)系
  • 本質(zhì)上MVP是面向接口編程,Model/View/Presenter每層的職責(zé)分工明確农猬,當(dāng)業(yè)務(wù)復(fù)雜時(shí)赡艰,整個(gè)流程邏輯也是很清晰的

當(dāng)然,MVP也不是十全十美的斤葱,MVP本身也存在以下問題:

  • View層會(huì)抽象成IView接口慷垮,并在IView中聲明一些列View相關(guān)的方法;同樣的揍堕,Presenter會(huì)被抽象成IPresenter接口及其一些列方法料身,每當(dāng)實(shí)現(xiàn)一個(gè)功能時(shí),都需要編寫多個(gè)接口及其對(duì)應(yīng)的方法鹤啡,實(shí)現(xiàn)起來(lái)相對(duì)比較繁瑣惯驼,而且每次有改動(dòng)時(shí),對(duì)應(yīng)的接口方法也基本都會(huì)再去改動(dòng)。
  • View層與Presenter層相互持有祟牲,當(dāng)View層關(guān)閉時(shí)隙畜,由于Presenter層不是生命周期感知的,可能會(huì)導(dǎo)致內(nèi)存泄漏甚至是崩潰说贝。

ps:如果你的項(xiàng)目中使用了RxJava议惰,可以使用 AutoDispose 自動(dòng)解綁。

MVVM

MVVM(Model-View-ViewModel)乡恕,架構(gòu)圖如下:

mvvm.png

MVVM各職責(zé)如下

  • Model模型:業(yè)務(wù)相關(guān)的數(shù)據(jù)(如網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)言询、本地?cái)?shù)據(jù)庫(kù)數(shù)據(jù)等)及其對(duì)數(shù)據(jù)的處理
  • View視圖:頁(yè)面視圖(Activity/Fragment),負(fù)責(zé)接收用戶輸入傲宜、發(fā)起數(shù)據(jù)請(qǐng)求及展示結(jié)果頁(yè)面
  • ViewModel:M與V之間的橋梁簇爆,負(fù)責(zé)業(yè)務(wù)邏輯

MVVM特點(diǎn)

  • View層接收用戶操作炸宵,并通過持有的ViewModel去處理業(yè)務(wù)邏輯遮晚,請(qǐng)求數(shù)據(jù)钉稍;
  • ViewModel層通過Model去獲取數(shù)據(jù),然后Model又將最新的數(shù)據(jù)傳回ViewModel層报嵌,到這里虱咧,ViewModel與Presenter所做的事基本是一樣的。但是ViewModel不會(huì)也不能持有View層的引用锚国,而是View層會(huì)通過觀察者模式監(jiān)聽ViewModel層的數(shù)據(jù)變化腕巡,當(dāng)有新數(shù)據(jù)時(shí),View層能自動(dòng)收到新數(shù)據(jù)并刷新界面血筑。

UI驅(qū)動(dòng) vs 數(shù)據(jù)驅(qū)動(dòng)

MVP中绘沉,Presenter中需要持有View層的引用,當(dāng)數(shù)據(jù)變化時(shí)云挟,需要主動(dòng)調(diào)用View層對(duì)應(yīng)的方法將數(shù)據(jù)傳過去并進(jìn)行UI刷新梆砸,這種可以認(rèn)為是UI驅(qū)動(dòng)转质;而MVVM中园欣,ViewModel并不會(huì)持有View層的引用,View層會(huì)監(jiān)聽數(shù)據(jù)變化休蟹,當(dāng)ViewModel中有數(shù)據(jù)更新時(shí)沸枯,View層能直接拿到新數(shù)據(jù)并完成UI更新,這種可以認(rèn)為是數(shù)據(jù)驅(qū)動(dòng)赂弓,顯然绑榴,MVVM相比于MVP來(lái)說(shuō)更加解耦。

MVVM的具體實(shí)現(xiàn)

上面介紹了MVC/MVP/MVVM的各自特點(diǎn)盈魁,其中MVC/MVP的具體使用方式翔怎,本文不再展開實(shí)現(xiàn),接下來(lái)主要聊一下MVVM的使用及封裝,MVVM也是官方推薦的架構(gòu)模式赤套。

Jetpack MVVM

Jetpack是官方推出的一系列組件庫(kù)飘痛,使用組件庫(kù)開發(fā)有很多好處,如:

  • 遵循最佳做法:采用最新的設(shè)計(jì)方法構(gòu)建容握,具有向后兼容性宣脉,可以減少崩潰和內(nèi)存泄漏
  • 消除樣板代碼:開發(fā)者可以更好地專注業(yè)務(wù)邏輯
  • 減少不一致:可以在各種Android版本中運(yùn)行,兼容性更好剔氏。

為了實(shí)現(xiàn)上面的MVVM架構(gòu)模式塑猖,Jetpack提供了多個(gè)組件來(lái)實(shí)現(xiàn),具體來(lái)說(shuō)有Lifecycle谈跛、LiveData羊苟、ViewModel(這里的ViewModel是MVVM中ViewModel層的具體實(shí)現(xiàn)),其中Lifecycle負(fù)責(zé)生命周期相關(guān)感憾;LiveData賦予類可觀察践险,同時(shí)還是生命周期感知的(內(nèi)部使用了Lifecycle);ViewModel旨在以注重生命周期的方式存儲(chǔ)和管理界面相關(guān)的數(shù)據(jù)吹菱,針對(duì)這幾個(gè)庫(kù)的詳細(xì)介紹及使用方式不再展開巍虫,有興趣的可以參見前面的文章:

通過這幾個(gè)庫(kù),就可以實(shí)現(xiàn)MVVM了鳍刷,官方也發(fā)布了MVVM的架構(gòu)圖:

Jetpack MVVM架構(gòu)圖.png

其中Activity/FragmentView層占遥,ViewModel+LiveDataViewModel層,為了統(tǒng)一管理網(wǎng)絡(luò)數(shù)據(jù)及本地?cái)?shù)據(jù)數(shù)據(jù)输瓜,又引入了Repository中間管理層瓦胎,本質(zhì)上是為了更好地管理數(shù)據(jù),為了簡(jiǎn)單把他們統(tǒng)稱為Model層吧尤揣。

使用舉例

  • View層代碼:
//MvvmExampleActivity.kt
class MvvmExampleActivity : BaseActivity() {

    private val mTvContent: TextView by id(R.id.tv_content)
    private val mBtnQuest: Button by id(R.id.btn_request)
    private val mToolBar: Toolbar by id(R.id.toolbar)

    override fun getLayoutId(): Int {
        return R.layout.activity_wan_android
    }

    override fun initViews() {
        initToolBar(mToolBar, "Jetpack MVVM", true)
    }

    override fun init() {
        //獲取ViewModel實(shí)例搔啊,注意這里不能直接new,因?yàn)閂iewModel的生命周期比Activity長(zhǎng)
        mViewModel = ViewModelProvider(this).get(WanViewModel::class.java)

        mBtnQuest.setOnClickListener {
            //請(qǐng)求數(shù)據(jù)
            mViewModel.getWanInfo()
        }

        //ViewModel中的LiveData注冊(cè)觀察者并監(jiān)聽數(shù)據(jù)變化
        mViewModel.mWanLiveData.observe(this) { list ->
            val builder = StringBuilder()
            for (index in list.indices) {
                //每條數(shù)據(jù)進(jìn)行折行顯示
                if (index != list.size - 1) {
                    builder.append(list[index])
                    builder.append("\n\n")
                } else {
                    builder.append(list[index])
                }
            }
            mTvContent.text = builder.toString()
        }
    }
}

  • ViewModel層代碼:
//WanViewModel.kt
class WanViewModel : ViewModel() {
    //LiveData
    val mWanLiveData = MutableLiveData<List<WanModel>>()
    //loading
    val loadingLiveData = SingleLiveData<Boolean>()
    //異常
    val errorLiveData = SingleLiveData<String>()

    //Repository中間層 管理所有數(shù)據(jù)來(lái)源 包括本地的及網(wǎng)絡(luò)的
    private val mWanRepo = WanRepository()

    fun getWanInfo(wanId: String = "") {
        //展示Loading
        loadingLiveData.postValue(true)
        viewModelScope.launch(Dispatchers.IO) {
            try {
                val result = mWanRepo.requestWanData(wanId)
                when (result.state) {
                    State.Success -> mWanLiveData.postValue(result.data)
                    State.Error -> errorLiveData.postValue(result.msg)
                }
            } catch (e: Exception) {
                error(e.message ?: "")
            } finally {
                loadingLiveData.postValue(false)
            }
        }
    }
}

  • Repository層(Model層)代碼:
class WanRepository {

    //請(qǐng)求網(wǎng)絡(luò)數(shù)據(jù)
    suspend fun requestWanData(drinkId: String): BaseData<List<WanModel>> {
        val service = RetrofitUtil.getService(DrinkService::class.java)

        val baseData = service.getBanner()
        if (baseData.code == 0) {
            //正確
            baseData.state = State.Success
        } else {
            //錯(cuò)誤
            baseData.state = State.Error
        }
        return baseData
    }
}

這里只通過Retrofit請(qǐng)求了網(wǎng)絡(luò)數(shù)據(jù) 玩Android 開放API北戏,如果需要添加本地?cái)?shù)據(jù)负芋,只需要在方法里添加本地?cái)?shù)據(jù)處理即可,即 Repository是數(shù)據(jù)的管理中間層嗜愈,對(duì)數(shù)據(jù)進(jìn)行統(tǒng)一管理旧蛾,ViewModel層中不需要關(guān)心數(shù)據(jù)的來(lái)源,大家各司其職即可蠕嫁,符合單一職責(zé)锨天,代碼可讀性更好,同時(shí)也更加解耦剃毒。在View層點(diǎn)擊按鈕請(qǐng)求數(shù)據(jù)病袄,執(zhí)行結(jié)果如下:

MVVM
以上就完成了一次網(wǎng)絡(luò)請(qǐng)求搂赋,相比于MVPMVVM既不用聲明多個(gè)接口及方法益缠,同時(shí)ViewModel也不會(huì)像Presenter那樣去持有View層的引用厂镇,而是生命周期感知的,MVVM方式更加解耦左刽。

封裝

上一節(jié)介紹了Jetpack MVVM的使用例子捺信,可以看到有一些代碼邏輯是可以抽離出來(lái)封裝到公共部分的,那么本節(jié)就嘗試對(duì)其做一次封裝欠痴。

首先迄靠,請(qǐng)求數(shù)據(jù)時(shí)可能會(huì)展示Loading,請(qǐng)求完后可能是空數(shù)據(jù)喇辽、錯(cuò)誤數(shù)據(jù)掌挚,對(duì)應(yīng)下面的IStatusView接口聲明:

interface IStatusView {
    fun showEmptyView() //空視圖
    fun showErrorView(errMsg: String) //錯(cuò)誤視圖
    fun showLoadingView(isShow: Boolean) //展示Loading視圖
}

因?yàn)閂iewModel是在Activity中初始化的,所以可以封裝成一個(gè)Base類:

abstract class BaseMvvmActivity<VM : BaseViewModel> : BaseActivity(), IStatusView {

    protected lateinit var mViewModel: VM
    protected lateinit var mView: View
    private lateinit var mLoadingDialog: LoadingDialog

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mLoadingDialog = LoadingDialog(this, false)
        mViewModel = getViewModel()!!
        init()
        registerEvent()
    }

    /**
     * 獲取ViewModel 子類可以復(fù)寫菩咨,自行初始化
     */
    protected open fun getViewModel(): VM? {
        //當(dāng)前對(duì)象超類的Type
        val type = javaClass.genericSuperclass
        //ParameterizedType表示參數(shù)化的類型
        if (type != null && type is ParameterizedType) {
            //返回此類型實(shí)際類型參數(shù)的Type對(duì)象數(shù)組
            val actualTypeArguments = type.actualTypeArguments
            val tClass = actualTypeArguments[0]
            return ViewModelProvider(this).get(tClass as Class<VM>)
        }
        return null
    }

    override fun showLoadingView(isShow: Boolean) {
        if (isShow) {
            mLoadingDialog.showDialog(this, false)
        } else {
            mLoadingDialog.dismissDialog()
        }
    }

    override fun showEmptyView() {
       ......
    }

    //錯(cuò)誤視圖 并且可以重試
    override fun showErrorView(errMsg: String) {
       .......
    }

    private fun registerEvent() {
       //接收錯(cuò)誤信息
       mViewModel.errorLiveData.observe(this) { errMsg ->
           showErrorView(errMsg)
       }
       //接收Loading信息
       mViewModel.loadingLiveData.observe(this, { isShow ->
           showLoadingView(isShow)
       })
    }

    abstract fun init()
}

Base類中初始化ViewModel吠式,還可以通過官方activity-ktxfragment-ktx擴(kuò)展庫(kù)抽米,初始化方式:val model: VM by viewModels()特占。

子類中繼承如下:

class MvvmExampleActivity : BaseMvvmActivity<WanViewModel>() {

    private val mTvContent: TextView by id(R.id.tv_content)
    private val mBtnQuest: Button by id(R.id.btn_request)
    private val mToolBar: Toolbar by id(R.id.toolbar)

    override fun getLayoutId(): Int {
        return R.layout.activity_wan_android
    }

    override fun initViews() {
        initToolBar(mToolBar, "Jetpack MVVM", true)
    }

    override fun init() {
        mBtnQuest.setOnClickListener {
            //請(qǐng)求數(shù)據(jù)
            mViewModel.getWanInfo()
        }
        /**
         * 這里使用了擴(kuò)展函數(shù),等同于mViewModel.mWanLiveData.observe(this) {}
         */
        observe(mViewModel.mWanLiveData) { list ->
            val builder = StringBuilder()
            for (index in list.indices) {
                //每條數(shù)據(jù)進(jìn)行折行顯示
                if (index != list.size - 1) {
                    builder.append(list[index])
                    builder.append("\n\n")
                } else {
                    builder.append(list[index])
                }
            }
            mTvContent.text = builder.toString()
        }
    }
}

我們把ViewModel的初始化放到了父類里進(jìn)行云茸,代碼看上去更簡(jiǎn)單了是目。監(jiān)聽數(shù)據(jù)變化mViewModel.mWanLiveData.observe(this) {} 方式改成observe(mViewModel.mWanLiveData) {}方式,少傳了一個(gè)LifecycleOwner标捺,其實(shí)這是一個(gè)擴(kuò)展函數(shù)懊纳,如下:

fun <T> LifecycleOwner.observe(liveData: LiveData<T>, observer: (t: T) -> Unit) {
    liveData.observe(this, { observer(it) })
}

ps:我們初始化View控件時(shí),如 private val mBtnQuest: Button by id(R.id.btn_request)亡容,依然使用了擴(kuò)展函數(shù)嗤疯,如下:

fun <T : View> Activity.id(id: Int) = lazy {
    findViewById<T>(id)
}

不用像寫java代碼中那樣時(shí)刻要想著判空,同時(shí)只會(huì)在使用時(shí)才會(huì)進(jìn)行初始化闺兢,很實(shí)用茂缚!

說(shuō)回來(lái),接著是ViewModel層的封裝列敲,BaseViewModel.kt

abstract class BaseViewModel : ViewModel() {
    //loading
    val loadingLiveData = SingleLiveData<Boolean>()
    //異常
    val errorLiveData = SingleLiveData<String>()

    /**
     * @param request 正常邏輯
     * @param error 異常處理
     * @param showLoading 請(qǐng)求網(wǎng)絡(luò)時(shí)是否展示Loading
     */
    fun launchRequest(
        showLoading: Boolean = true,
        error: suspend (String) -> Unit = { errMsg ->
            //默認(rèn)異常處理阱佛,子類可以進(jìn)行覆寫
            errorLiveData.postValue(errMsg)
        }, request: suspend () -> Unit
    ) {
        //是否展示Loading
        if (showLoading) {
            loadStart()
        }
      
        //使用viewModelScope.launch開啟協(xié)程
        viewModelScope.launch(Dispatchers.IO) {
            try {
                request()
            } catch (e: Exception) {
                error(e.message ?: "")
            } finally {
                if (showLoading) {
                    loadFinish()
                }
            }
        }
    }

    private fun loadStart() {
        loadingLiveData.postValue(true)
    }

    private fun loadFinish() {
        loadingLiveData.postValue(false)
    }
}

擴(kuò)展一下1、上面執(zhí)行網(wǎng)絡(luò)請(qǐng)求時(shí)戴而,使用viewModelScope.launch來(lái)啟動(dòng)協(xié)程,引入方式:

implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'

這樣就可以直接在ViewModel中啟動(dòng)協(xié)程并且當(dāng)ViewModel生命周期結(jié)束時(shí)協(xié)程也會(huì)自動(dòng)關(guān)閉翩蘸,避免使用GlobalScope.launch { }MainScope().launch { }還需自行關(guān)閉協(xié)程所意, 當(dāng)然,如果是在Activity/Fragment、liveData中使用協(xié)程扶踊,也可以按需引入:

implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'

具體可以參見官方的 將 Kotlin 協(xié)程與生命周期感知型組件一起使用 這篇文章泄鹏。

2、另外細(xì)心的讀者可能觀察到秧耗,上面我們的Loading备籽、Error信息監(jiān)聽都是用的SingleLiveData,把這個(gè)類打代碼貼一下:

/**
 * 多個(gè)觀察者存在時(shí)分井,只有一個(gè)Observer能夠收到數(shù)據(jù)更新
 * https://github.com/android/architecture-samples/blob/dev-todo-mvvm-live/todoapp/app/src/main/java/com/example/android/architecture/blueprints/todoapp/SingleLiveEvent.java
 */
class SingleLiveData<T> : MutableLiveData<T>() {
    companion object {
        private const val TAG = "SingleLiveEvent"
    }
    private val mPending = AtomicBoolean(false)

    @MainThread
    override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
        if (hasActiveObservers()) {
            Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
        }
        // Observe the internal MutableLiveData
        super.observe(owner) { t ->
            //如果expect為true车猬,那么將值update為false,方法整體返回true尺锚,
            //即當(dāng)前Observer能夠收到更新珠闰,后面如果還有訂閱者,不能再收到更新通知了
            if (mPending.compareAndSet(true, false)) {
                observer.onChanged(t)
            }
        }
    }

    override fun setValue(@Nullable value: T?) {
        //AtomicBoolean中設(shè)置的值設(shè)置為true
        mPending.set(true)
        super.setValue(value)
    }

    /**
     * Used for cases where T is Void, to make calls cleaner.
     */
    @MainThread
    fun call() {
        value = null
    }
}

可以看到SingleLiveData還是繼承自MutableLiveData瘫辩,區(qū)別是當(dāng)多個(gè)觀察者存在時(shí)伏嗜,只有一個(gè)Observer能夠收到數(shù)據(jù)更新,本質(zhì)上是在observe()時(shí)通過CAS加了限制伐厌,注釋已經(jīng)很詳細(xì)了承绸,不再贅述。

子類中繼承如下:

class WanViewModel : BaseViewModel() {
    //LiveData
    val mWanLiveData = MutableLiveData<List<WanModel>>()

    //Repository中間層 管理所有數(shù)據(jù)來(lái)源 包括本地的及網(wǎng)絡(luò)的
    private val mWanRepo = WanRepository()

    fun getWanInfo(wanId: String = "") {
        launchRequest {
            val result = mWanRepo.requestWanData(wanId)
            when (result.state) {
                State.Success -> mWanLiveData.postValue(result.data)
                State.Error -> errorLiveData.postValue(result.msg)
            }
        }
    }
}

最后是對(duì)Model層的封裝挣轨,BaseRepository.kt

open class BaseRepository {
    suspend fun <T : Any> executeRequest(
        block: suspend () -> BaseData<T>
    ): BaseData<T> {
        val baseData = block.invoke()
        if (baseData.code == 0) {
            //正確
            baseData.state = State.Success
        } else {
            //錯(cuò)誤
            baseData.state = State.Error
        }
        return baseData
    }
}

數(shù)據(jù)基類BaseData.kt

class BaseData<T> {
    @SerializedName("errorCode")
    var code = -1
    @SerializedName("errorMsg")
    var msg: String? = null
    var data: T? = null
    var state: State = State.Error
}

enum class State {
    Success, Error
}

子類中繼承如下:

class WanRepository : BaseRepository() {
    suspend fun requestWanData(drinkId: String): BaseData<List<WanModel>> {
        val service = RetrofitUtil.getService(DrinkService::class.java)
        return executeRequest {
            service.getBanner()
        }
    }
}

到此八酒,基本上就完成了,代碼中有一些細(xì)節(jié)沒貼出來(lái)刃唐,完整代碼參見:Jetpack MVVM

參考

【1】Android應(yīng)用架構(gòu)指南
【2】www.php.cn/faq/417265.…

相關(guān)推薦
Android架構(gòu)模式實(shí)戰(zhàn)-MVC羞迷、MVP、MVVM+jetpack_嗶哩嗶哩_bilibili
Android項(xiàng)目實(shí)戰(zhàn)—MVVM與Jetpack架構(gòu)核心技術(shù)databinding解密_嗶哩嗶哩_bilibili
Android開發(fā)項(xiàng)目實(shí)戰(zhàn)——jetpack與MVC画饥、MVP衔瓮、MVVM的項(xiàng)目實(shí)戰(zhàn)

本文轉(zhuǎn)自 https://juejin.cn/post/7010954321785257997,如有侵權(quán)抖甘,請(qǐng)聯(lián)系刪除热鞍。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市衔彻,隨后出現(xiàn)的幾起案子薇宠,更是在濱河造成了極大的恐慌,老刑警劉巖艰额,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件澄港,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡柄沮,警方通過查閱死者的電腦和手機(jī)回梧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門废岂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人狱意,你說(shuō)我怎么就攤上這事湖苞。” “怎么了详囤?”我有些...
    開封第一講書人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵财骨,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我藏姐,道長(zhǎng)隆箩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任包各,我火速辦了婚禮摘仅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘问畅。我一直安慰自己娃属,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開白布护姆。 她就那樣靜靜地躺著矾端,像睡著了一般。 火紅的嫁衣襯著肌膚如雪卵皂。 梳的紋絲不亂的頭發(fā)上秩铆,一...
    開封第一講書人閱讀 49,185評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音灯变,去河邊找鬼殴玛。 笑死,一個(gè)胖子當(dāng)著我的面吹牛添祸,可吹牛的內(nèi)容都是我干的滚粟。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼刃泌,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼凡壤!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起耙替,我...
    開封第一講書人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤亚侠,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后俗扇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體硝烂,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年狐援,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了钢坦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片究孕。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡啥酱,死狀恐怖爹凹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情镶殷,我是刑警寧澤禾酱,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站绘趋,受9級(jí)特大地震影響颤陶,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜陷遮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一滓走、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧帽馋,春花似錦搅方、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至吧慢,卻和暖如春涛漂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背检诗。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工匈仗, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人逢慌。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓悠轩,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親涕癣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子哗蜈,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容