10、JetPack之MVVM架构

View:Framgnet/Activity

ViewModel:ViweModel是为View管理数据的,也负责业务逻辑处理

Model: 对应Repository,处理操作数据,domain等

在这里插入图片描述

MVVM:

  • 尽量不要依赖View
  • 模型驱动界面、独立于View对象和组件,不受View/组件生命周期影响
  • 解决MVC中存在的问题
    • 把逻辑拆分到各个ViewModel里,Activity/fragment只负责UI的绑定和UI事件监听
  • 解决MVP中存在的问题
    • View层持有ViewModel的引用,调用里面的方法
    • 监听ViewModel里的数据更新UI,你需要什么监听什么,而不是像Presenter全部通知到各个View层,需要View层实现所有的接口

在MMVM架构中都是向下依赖的

View层依赖ViewModel、ViewModel依赖Model层,并不存在逆向的过程

案例:使用MVVM实现特惠界面的逻辑功能

View层:OnSellActivity--特惠的界面,只关心对view的监听,点击事件等

package com.example.jetpackbysob.taobao

import android.graphics.Rect
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.jetpackbysob.R
import com.example.jetpackbysob.taobao.adapter.OnSellListAdapter
import com.example.jetpackbysob.taobao.base.LoadState
import com.lcodecore.tkrefreshlayout.RefreshListenerAdapter
import com.lcodecore.tkrefreshlayout.TwinklingRefreshLayout
import kotlinx.android.synthetic.main.activity_on_sell.*

/**
 * Project_name:JetPackBySob
 * Created by:ChenFuXu.
 * Date: 2022/5/12 22:54
 */
class OnSellActivity : AppCompatActivity() {

    private val mOnSellViewModel by lazy {
        ViewModelProvider(this).get(OnSellViewModel::class.java)
    }

    private val mOnSellAdapter by lazy {
        OnSellListAdapter()
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_on_sell)
        initView()
        initDataObserver()
    }

    /**
     * 观察数据变化
     */
    private fun initDataObserver() {
        mOnSellViewModel.apply {
            // 对特惠界面内容的监听
            mContentList.observe(this@OnSellActivity) {
                // 数据发生变化内容
                // 更新适配器
                mOnSellAdapter.setData(it)
            }
            // 对页面加载状态的监听
            mLoadState.observe(this@OnSellActivity) {
                // 根据加载的状态来更新UI
                if (it != LoadState.LOAD_MORE_LOADING) {
                    // 不是刷新的Loading,才隐藏所有的界面
                    hideAllView()
                }
                when (it) {
                    LoadState.LOADING -> {
                        on_loading.visibility = View.VISIBLE
                    }
                    LoadState.SUCCESS -> {
                        content_flush.visibility = View.VISIBLE
                        content_flush.finishLoadmore()
                    }
                    LoadState.NETWORK_ERROR -> {
                        on_network_error.visibility = View.VISIBLE
                    }
                    LoadState.EMPTY -> {
                        on_data_empty.visibility = View.VISIBLE
                    }
                    LoadState.LOAD_MORE_LOADING -> {
                        Toast.makeText(this@OnSellActivity, "加载更多数据", Toast.LENGTH_SHORT).show()
                    }
                    LoadState.LOAD_MORE_ERROR -> {
                        Toast.makeText(this@OnSellActivity, "网络错误", Toast.LENGTH_SHORT).show()
                        content_flush.finishLoadmore()
                    }
                    LoadState.LOAD_MORE_EMPTY -> {
                        Toast.makeText(this@OnSellActivity, "无更多数据", Toast.LENGTH_SHORT).show()
                        content_flush.finishLoadmore()
                    }
                    else -> {}
                }
            }
        }.loadContent()
    }

    private fun initView() {
        content_flush.run {
            setEnableLoadmore(true)
            setEnableRefresh(false)
            setEnableOverScroll(true)
            setOnRefreshListener(object: RefreshListenerAdapter() {
                override fun onLoadMore(refreshLayout: TwinklingRefreshLayout?) {
                    // 去执行加载更多
                    mOnSellViewModel.loadMore()
                }
            })
        }

        content_list_rv.run {
            layoutManager = LinearLayoutManager(this@OnSellActivity)
            adapter = mOnSellAdapter
            addItemDecoration(
                object : RecyclerView.ItemDecoration() {
                    override fun getItemOffsets(
                        outRect: Rect,
                        view: View,
                        parent: RecyclerView,
                        state: RecyclerView.State
                    ) {
                        outRect.apply {
                            top = 15
                            bottom = 15
                            left = 15
                            right = 15
                        }
                    }
                }
            )
        }

        // 点击网络错误界面重新加载
        on_network_error.setOnClickListener {
            mOnSellViewModel.loadContent()
        }
    }

    /**
     * 隐藏所有的view
     */
    private fun hideAllView() {
        content_flush.visibility = View.GONE
        on_loading.visibility = View.GONE
        on_data_empty.visibility = View.GONE
        on_network_error.visibility = View.GONE
    }
    
    override fun onDestroy() {
        super.onDestroy()
    }
}

View层持有ViewModel对象 

10、JetPack之MVVM架构_第1张图片 通过ViewModel获取到里面需要监听的数据,并添加观察者,在数据发生变化的时候进行相应的UI处理

10、JetPack之MVVM架构_第2张图片

ViewModel层:OnSellViewModel 里面定义了需要被监听的数据,处理业务逻辑,依赖于Model层的对象OnSellRepository

package com.example.jetpackbysob.taobao

import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.jetpackbysob.taobao.base.LoadState
import com.example.jetpackbysob.taobao.domain.OnSellBean
import kotlinx.coroutines.launch

/**
 * Project_name:JetPackBySob
 * Created by:ChenFuXu.
 * Date: 2022/5/12 23:06
 */
class OnSellViewModel : ViewModel() {
    companion object {
        private const val TAG = "OnSellViewModel"

        // 默认为第一页
        const val DEFAULT_PAGE = 1
    }

    // 观察的数据对象
    val mContentList = MutableLiveData>()

    // 观察的页面加载状态
    val mLoadState = MutableLiveData()

    // 当前页
    private var mCurrentPage = DEFAULT_PAGE

    private val mOnSellRepository by lazy {
        OnSellRepository()
    }

    private var mIsLoadMore = false

    /**
     * 加载首页内容
     */
    fun loadContent() {
        mIsLoadMore = false
        // 将页面状态置为Loading
        mLoadState.value = LoadState.LOADING
        this.listContentByPage(mCurrentPage)
    }

    private fun listContentByPage(page: Int) {
        // 使用挂起的方式
        viewModelScope.launch {
            try {
                // 请求数据成功
                val onSellList = mOnSellRepository.getOnSellList(page)
                val oldValue = mContentList.value?: mutableListOf()
                oldValue.addAll(onSellList.tbk_dg_optimus_material_response.result_list.map_data)
                Log.d(TAG, "cfx listContentByPage 请求数据成功")
                if (onSellList.tbk_dg_optimus_material_response.result_list.map_data.isNotEmpty()) {
                    Log.d(TAG, "cfx onSellList " + onSellList.tbk_dg_optimus_material_response.result_list.map_data.size)
                    // 设置数据
                    mContentList.value = oldValue
                    // 将页面状态置为SUCCESS
                    mLoadState.value = LoadState.SUCCESS
                } else {
                    Log.d(TAG, "cfx listContentByPage 请求数据成功 数据内容为空")
                    // 将页面状态置为EMPTY
                    mLoadState.value = if (mIsLoadMore) LoadState.LOAD_MORE_EMPTY else LoadState.EMPTY
                }
            } catch (e: Exception) {
                mCurrentPage--
                // 请求数据失败
                mLoadState.value = if (mIsLoadMore) LoadState.LOAD_MORE_ERROR else LoadState.NETWORK_ERROR
                Log.d(TAG, "cfx listContentByPage 请求数据失败 e: $e")
            }
        }
    }

    /**
     * 上拉加载更多
     */
    fun loadMore() {
        mIsLoadMore = true
        mLoadState.value = LoadState.LOAD_MORE_LOADING
        Log.d(TAG, "cfx loadMore")
        // 去加载更多内容
        mCurrentPage++
        this.listContentByPage(mCurrentPage)
    }
}

通过Model层获取数据,返回数据结果后,更新设置数据,在view层对数据添加的观察者会被通知到当前数据发生改变了,进而通知UI更新 

10、JetPack之MVVM架构_第3张图片

 Model层:OnSellRepository、请求数据,数据实体类,网络请求,domain等

这里OnSellRepository主要通过Retrofit请求数据获取特惠列表

package com.example.jetpackbysob.taobao

import com.example.jetpackbysob.taobao.api.RetrofitClient

/**
 * Project_name:JetPackBySob
 * Created by:ChenFuXu.
 * Date: 2022/5/13 20:38
 */
class OnSellRepository {
    // 获取特惠列表
    suspend fun getOnSellList(page: Int) = RetrofitClient.mApiService.getOnSellList(page).apiData()
}

 网络请求Api

package com.example.jetpackbysob.taobao.api

import com.example.jetpackbysob.taobao.domain.OnSellBean
import com.example.jetpackbysob.taobao.domain.ResultBean
import retrofit2.http.GET
import retrofit2.http.Path

/**
 * Project_name:JetPackBySob
 * Created by:ChenFuXu.
 * Date: 2022/5/13 20:50
 */
interface ApiService {
    companion object {
        const val BASE_URL = "https://api.sunofbeaches.com/shop/"
    }

    @GET("onSell/{page}")
    suspend fun getOnSellList(@Path("page") page: Int): ResultBean
}

构建的Retrofit客户端 

package com.example.jetpackbysob.taobao.api

import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit

/**
 * Project_name:JetPackBySob
 * Created by:ChenFuXu.
 * Date: 2022/5/13 20:49
 *
 * 创建Retrofit 已经提供ApiService
 */
object RetrofitClient {
    private val okHttpClient: OkHttpClient = OkHttpClient.Builder()
        .callTimeout(30, TimeUnit.SECONDS)
        .build()

    private val mRetrofit: Retrofit = Retrofit.Builder()
        .baseUrl(ApiService.BASE_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .client(okHttpClient)
        .build()

    val mApiService: ApiService = mRetrofit.create(ApiService::class.java)
}

页面加载状态的枚举类 

package com.example.jetpackbysob.taobao.base

/**
 * Project_name:JetPackBySob
 * Created by:ChenFuXu.
 * Date: 2022/5/14 16:12
 *
 * 页面的加载状态
 */
enum class LoadState {
    LOADING,
    SUCCESS,
    NETWORK_ERROR,
    EMPTY,
    LOAD_MORE_LOADING,
    LOAD_MORE_SUCCESS,
    LOAD_MORE_EMPTY,
    LOAD_MORE_ERROR
}

结果实体类 

package com.example.jetpackbysob.taobao.domain

import com.example.jetpackbysob.taobao.api.ApiException

/**
 * Project_name:JetPackBySob
 * Created by:ChenFuXu.
 * Date: 2022/5/13 21:00
 *
 * 特惠界面内容实体类
 */
data class ResultBean(
    val success: Boolean,
    val code: Int,
    val message: String,
    val data: T
) {
    companion object {
        const val CODE_SUCCESS = 10000
    }

    fun apiData(): T {
        // 如果成功的code,我们返回数据,否则抛出异常
        if (code == CODE_SUCCESS) {
            return data
        } else {
            throw ApiException(code, message)
        }

    }
}

你可能感兴趣的:(JetPack,ui)