View:Framgnet/Activity
ViewModel:ViweModel是为View管理数据的,也负责业务逻辑处理
Model: 对应Repository,处理操作数据,domain等
MVVM:
在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对象
通过ViewModel获取到里面需要监听的数据,并添加观察者,在数据发生变化的时候进行相应的UI处理
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更新
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)
}
}
}