【Android】MVI架构快速入门:从双向绑定到单向数据流

您所在的位置:网站首页 mvi是什么缩写 【Android】MVI架构快速入门:从双向绑定到单向数据流

【Android】MVI架构快速入门:从双向绑定到单向数据流

2023-11-27 22:52| 来源: 网络整理| 查看: 265

在这里插入图片描述

现在从事Android开发的,多少都要懂点架构知识,从MVC、MVP再到MVVM,想必大家对于其各自的优缺点早已如数家珍。今天介绍的MVI与MVVM非常接近,可以更针对性地解决一些MVVM中解决不了的问题

何为MVI?

在这里插入图片描述 MVI即Model-View-Intent,它受Cycle.js前端框架的启发,提倡一种单向数据流的设计思想,非常适合数据驱动型的UI展示项目:

Model: 与其他MVVM中的Model不同的是,MVI的Model主要指UI状态(State)。当前界面展示的内容无非就是UI状态的一个快照:例如数据加载过程、控件位置等都是一种UI状态View: 与其他MVX中的View一致,可能是一个Activity、Fragment或者任意UI承载单元。MVI中的View通过订阅Intent的变化实现界面刷新(不是Activity的Intent、后面介绍)Intent: 此Intent不是Activity的Intent,用户的任何操作都被包装成Intent后发送给Model进行数据请求 单向数据流

用户操作以Intent的形式通知Model => Model基于Intent更新State => View接收到State变化刷新UI。数据永远在一个环形结构中单向流动,不能反向流动: 在这里插入图片描述 这种单向数据流结构的MVI有什么优缺点呢?

优点

UI的所有变化来自State,所以只需聚焦State,架构更简单、易于调试数据单向流动,很容易对状态变化进行跟踪和回溯state实例都是不可变的,确保线程安全UI只是反应State的变化,没有额外逻辑,可以被轻松替换或复用

缺点

所有的操作最终都会转换成State,所以当复杂页面的State容易膨胀state是不变的,每当state需要更新时都要创建新对象替代老对象,这会带来一定内存开销有些事件类的UI变化不适合用state描述,例如弹出一个toast或者snackbar

talk is cheap, show me the code,我们通过一个Sample看一下如何快速搭建一个MVI架构的项目。

代码示例

代码结构如下: Sample中的依赖库

// Added Dependencies implementation "androidx.recyclerview:recyclerview:1.1.0" implementation 'android.arch.lifecycle:extensions:1.1.1' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0' implementation 'com.github.bumptech.glide:glide:4.11.0' //retrofit implementation 'com.squareup.retrofit2:retrofit:2.8.1' implementation "com.squareup.retrofit2:converter-moshi:2.6.2" //Coroutine implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.6" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.6"

代码中使用以下API进行请求

https://reqres.in/api/users

将得到结果: 在这里插入图片描述

1. 数据层 1.1 User

定义User的data class

package com.my.mvi.data.model data class User( @Json(name = "id") val id: Int = 0, @Json(name = "first_name") val name: String = "", @Json(name = "email") val email: String = "", @Json(name = "avator") val avator: String = "" ) 1.2 ApiService

定义ApiService,getUsers方法进行数据请求

package com.my.mvi.data.api interface ApiService { @GET("users") suspend fun getUsers(): List } 1.3 Retrofit

创建Retrofit实例

object RetrofitBuilder { private const val BASE_URL = "https://reqres.in/api/user/1" private fun getRetrofit() = Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(MoshiConverterFactory.create()) .build() val apiService: ApiService = getRetrofit().create(ApiService::class.java) } 1.4 Repository

定义Repository,封装API请求的具体实现

package com.my.mvi.data.repository class MainRepository(private val apiService: ApiService) { suspend fun getUsers() = apiService.getUsers() } 2. UI层

Model定义完毕后,开始定义UI层,包括View、ViewModel以及Intent的定义

2.1 RecyclerView.Adapter

首先,需要一个RecyclerView来呈现列表结果,定义MainAdapter如下:

package com.my.mvi.ui.main.adapter class MainAdapter( private val users: ArrayList ) : RecyclerView.Adapter() { class DataViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { fun bind(user: User) { itemView.textViewUserName.text = user.name itemView.textViewUserEmail.text = user.email Glide.with(itemView.imageViewAvatar.context) .load(user.avatar) .into(itemView.imageViewAvatar) } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = DataViewHolder( LayoutInflater.from(parent.context).inflate( R.layout.item_layout, parent, false ) ) override fun getItemCount(): Int = users.size override fun onBindViewHolder(holder: DataViewHolder, position: Int) = holder.bind(users[position]) fun addData(list: List) { users.addAll(list) } }

item_layout.xml

2.2 Intent

定义Intent用来包装用户Action

package com.my.mvi.ui.main.intent sealed class MainIntent { object FetchUser : MainIntent() } 2.3 State

定义UI层的State结构体

sealed class MainState { object Idle : MainState() object Loading : MainState() data class Users(val user: List) : MainState() data class Error(val error: String?) : MainState() } 2.4 ViewModel

ViewModel是MVI的核心,存放和管理State,同时接受Intent并进行数据请求

package com.my.mvi.ui.main.viewmodel class MainViewModel( private val repository: MainRepository ) : ViewModel() { val userIntent = Channel(Channel.UNLIMITED) private val _state = MutableStateFlow(MainState.Idle) val state: StateFlow get() = _state init { handleIntent() } private fun handleIntent() { viewModelScope.launch { userIntent.consumeAsFlow().collect { when (it) { is MainIntent.FetchUser -> fetchUser() } } } } private fun fetchUser() { viewModelScope.launch { _state.value = MainState.Loading _state.value = try { MainState.Users(repository.getUsers()) } catch (e: Exception) { MainState.Error(e.localizedMessage) } } } }

我们在handleIntent中订阅userIntent并根据Action类型执行相应操作。本case中当出现FetchUser的Action时,调用fetchUser方法请求用户数据。用户数据返回后,会更新State,MainActivity订阅此State并刷新界面。

2.5 ViewModelFactory

构造ViewModel需要Repository,所以通过ViewModelFactory注入必要的依赖

class ViewModelFactory(private val apiService: ApiService) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { if (modelClass.isAssignableFrom(MainViewModel::class.java)) { return MainViewModel(MainRepository(apiService)) as T } throw IllegalArgumentException("Unknown class name") } }

2.6 定义MainActivity

package com.my.mvi.ui.main.view class MainActivity : AppCompatActivity() { private lateinit var mainViewModel: MainViewModel private var adapter = MainAdapter(arrayListOf()) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) setupUI() setupViewModel() observeViewModel() setupClicks() } private fun setupClicks() { buttonFetchUser.setOnClickListener { lifecycleScope.launch { mainViewModel.userIntent.send(MainIntent.FetchUser) } } } private fun setupUI() { recyclerView.layoutManager = LinearLayoutManager(this) recyclerView.run { addItemDecoration( DividerItemDecoration( recyclerView.context, (recyclerView.layoutManager as LinearLayoutManager).orientation ) ) } recyclerView.adapter = adapter } private fun setupViewModel() { mainViewModel = ViewModelProviders.of( this, ViewModelFactory( ApiHelperImpl( RetrofitBuilder.apiService ) ) ).get(MainViewModel::class.java) } private fun observeViewModel() { lifecycleScope.launch { mainViewModel.state.collect { when (it) { is MainState.Idle -> { } is MainState.Loading -> { buttonFetchUser.visibility = View.GONE progressBar.visibility = View.VISIBLE } is MainState.Users -> { progressBar.visibility = View.GONE buttonFetchUser.visibility = View.GONE renderList(it.user) } is MainState.Error -> { progressBar.visibility = View.GONE buttonFetchUser.visibility = View.VISIBLE Toast.makeText(this@MainActivity, it.error, Toast.LENGTH_LONG).show() } } } } } private fun renderList(users: List) { recyclerView.visibility = View.VISIBLE users.let { listOfUsers -> listOfUsers.let { adapter.addData(it) } } adapter.notifyDataSetChanged() } }

MainActivity中订阅mainViewModel.state,根据State处理各种UI显示和刷新。

activity_main.xml:

如上,一个完整的MVI项目完成了。

最后

MVI在MVVM的基础上,规定了数据的单向流动和状态的不可变性,这类似于前端的Redux思想,非常适合UI展示类的场景。MVVM也好,MVI也好都不是架构的最终形态,世界上没有完美的架构,要根据项目情况选择适合的架构进行开发。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3