2.MVVM架构图
3. MVVM 实现一个具体业务
3 .1 界面层的实现
界面层实现时,需要遵循以下几点。
1) 选择实现界面的元素
界面元素可以用 view 或 compose 来实现,这里用 view 实现。
2) 提供一个状态容器
这里使用 ViewModel 作为状态容器;状态容器用来存放界面状态变量;ViewModel 是官方推荐的状态容器,而不是必须使用它作为状态容器。
3) 定义界面状态
这个需求中我们根据业务描述,定义出多个界面状态。
Kotlin
/**
* 加载失败 UI 状态,显示失败图
* 首屏获取的数据为空、首屏请求数据失败时展示失败图
* 初始值:隐藏
*/
val loadingError: StateFlow<Boolean>
get() = _loadingError
private val _loadingError = MutableStateFlow<Boolean>(false)
/**
* 正在加载 UI 状态,显示加载中图
* 首屏时请求网络时展示加载中图
* 初始值:展示
*/
val isLoading: StateFlow<Boolean>
get() = _isLoading
private val _isLoading = MutableStateFlow<Boolean>(true)
/**
* 加载成功后回来的列表 UI 状态,将 list 数据展示到列表上
*/
val newsList: StateFlow<MutableList<News>>
get() = _newsList
private val _newsList = MutableStateFlow<MutableList<News>>(mutableListOf())
/**
* 加载完成 UI 状态
*/
val loadingFinish: StateFlow<Boolean>
get() = _loadingFinish
private val _loadingFinish = MutableStateFlow<Boolean>(false)
/**
* 界面 toast UI 状态
*/
val toastMessage: StateFlow<String>
get() = _toastMessage
private val _toastMessage = MutableStateFlow<String>("")
4) 公开界面状态
这里选择数据流 StateFlow 公开界面状态。当然也可以选择 LiveData 公开界面状态。
5) 使用/订阅界面状态
我这里使用的是数据流 StateFlow 公开的界面状态,所以在界面层相对应的使用 flow#collect 订阅界面状态。
数据模型驱动界面
结合上面几点,界面层的实现代码为:
3.1.1界面元素的实现:
Kotlin
class NewsActivity: ComponentActivity() {
private var mBinding: ActivityNewsBinding? = null
private var mAdapter: NewsListAdapter? = null
private val mViewModel = NewsViewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding = ActivityNewsBinding.inflate(layoutInflater)
setContentView(mBinding?.root)
initView()
initObserver()
initData()
}
private fun initView() {
mBinding?.listView?.layoutManager = LinearLayoutManager(this)
mAdapter = NewsListAdapter()
mBinding?.listView?.adapter = mAdapter
mBinding?.refreshView?.setOnRefreshListener {
mViewModel.refreshNewsData()
}
}
private fun initData() {
mViewModel.getNewsData()
}
private fun initObserver() {
lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) {
launch {
mViewModel.isLoading.collect {
if (it) {
mBinding?.loadingView?.visibility = View.VISIBLE
} else {
mBinding?.loadingView?.visibility = View.GONE
}
}
}
launch {
mViewModel.loadingError.collect {
if (it) {
mBinding?.loadingError?.visibility = View.VISIBLE
} else {
mBinding?.loadingError?.visibility = View.GONE
}
}
}
launch {
mViewModel.loadingFinish.collect {
if (it) {
mBinding?.refreshView?.isRefreshing = false
}
}
}
launch {
mViewModel.toastMessage.collect {
if (it.isNotEmpty()) {
showToast(it)
}
}
}
launch {
mViewModel.newsList.collect {
if (it.isNotEmpty()) {
mBinding?.loadingError?.visibility = View.GONE
mBinding?.loadingView?.visibility = View.GONE
mBinding?.refreshView?.visibility = View.VISIBLE
mAdapter?.setData(it)
}
}
}
}
}
}
}
3.1.2状态容器的实现:
Kotlin
class NewsViewModel : ViewModel() {
private val repository = NewsRepository()
/**
* 加载失败 UI 状态,显示失败图
* 首屏获取的数据为空、首屏请求数据失败时展示失败图
* 初始值:隐藏
*/
val loadingError: StateFlow<Boolean>
get() = _loadingError
private val _loadingError = MutableStateFlow<Boolean>(false)
/**
* 正在加载 UI 状态,显示加载中图
* 首屏时请求网络时展示加载中图
* 初始值:展示
*/
val isLoading: StateFlow<Boolean>
get() = _isLoading
private val _isLoading = MutableStateFlow<Boolean>(true)
/**
* 加载成功后回来的列表 UI 状态,将 list 数据展示到列表上
*/
val newsList: StateFlow<MutableList<News>>
get() = _newsList
private val _newsList = MutableStateFlow<MutableList<News>>(mutableListOf())
/**
* 加载完成 UI 状态
*/
val loadingFinish: StateFlow<Boolean>
get() = _loadingFinish
private val _loadingFinish = MutableStateFlow<Boolean>(false)
/**
* 界面 toast UI 状态
*/
val toastMessage: StateFlow<String>
get() = _toastMessage
private val _toastMessage = MutableStateFlow<String>("")
fun getNewsData() {
viewModelScope.launch(Dispatchers.IO) {
val list = repository.getNewsList()
if (list.isNullOrEmpty()) {
_loadingError.emit(true)
} else {
_newsList.emit(list)
}
}
}
fun refreshNewsData() {
viewModelScope.launch(Dispatchers.IO) {
val list = repository.getNewsList()
_loadingFinish.emit(true)
if (list.isNullOrEmpty()) {
_toastMessage.emit("暂时没有更新数据")
} else {
_newsList.emit(list)
}
}
}
}
3 .2 数据层的实现
这里的数据层只有一个新闻列表数据结构的存储仓库 NewsRepository,另外获取新闻信息属于一次性操作,根据数据层架构设计,直接使用 suspend 就好。
Kotlin
class NewsRepository {
suspend fun getNewsList(): MutableList<News>? {
delay(2000)
val list = mutableListOf<News>()
val news = News("标题", "描述信息")
list.add(news)
list.add(news)
list.add(news)
list.add(news)
return list
}
}
3 .3网域层的实现
网域层是可选的,是否具备网域层,跟架构是否为 MVVM 无关,这个案例中不适用网域层。