APP架构设计_2.用MVVM架构实现一个具体业务

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 无关,这个案例中不适用网域层。

相关推荐
西陵几秒前
为什么说 AI 赋能前端开发,已经不是选择题,而是必然趋势?
前端·架构·ai编程
国科安芯19 分钟前
AS32S601型MCU芯片电源管理(PMU)模块详解
单片机·嵌入式硬件·性能优化·架构·risc-v
六行神算API-天璇1 小时前
架构思考:大模型作为医疗科研的“智能中间件”
人工智能·中间件·架构·数据挖掘·ar
济南壹软网络科技有限公司2 小时前
企业级盲盒系统:Java高并发架构在多元化抽奖电商中的设计与实践
java·架构·开源源码·盲盒源码·盲盒h5·盲盒app
鹏说大数据3 小时前
数据治理项目实战系列6-数据治理架构设计实战,流程 + 工具双架构拆解
大数据·数据库·架构
一水鉴天3 小时前
整体设计 定稿 之26 重构和改造现有程序结构 之2 (codebuddy)
开发语言·人工智能·重构·架构
隐语SecretFlow4 小时前
【隐语Secreflow】如何配置 Kuscia 对请求进行 Path Rewrit
架构·开源
小二·4 小时前
MyBatis基础入门《十四》多租户架构实战:基于 MyBatis 实现 SaaS 系统的动态数据隔离
数据库·架构·mybatis
老前端的功夫4 小时前
Vue 3 vs Vue 2 深度解析:从架构革新到开发体验全面升级
前端·vue.js·架构
测试人社区-小明6 小时前
智能测试误报问题的深度解析与应对策略
人工智能·opencv·线性代数·微服务·矩阵·架构·数据挖掘