MVI 就像是 MVC 的一种衍生产物, 我们知道 MVP 也是 MVC 的衍生产物,MVVM 也是 MVP 的一种衍生

MVI 的愿景是能让 View 触发刷新的状态只有一个。
MVI 各层
MVI 将架构分成了三个部分:
Model: 与MVVM中的Model不同的是,MVI的Model主要指UI状态(State)。例如页面加载状态、控件位置等都是一种UI状态
View: 与其他MVX中的View一致,可能是一个Activity或者任意UI承载单元。MVI中的View通过订阅Model的变化实现界面刷新
Intent: 此Intent不是Activity的Intent,用户的任何操作都被包装成Intent后发送给Model层进行数据请求

MVI强调数据的单向流动,主要分为以下几步:
用户操作以Intent的形式通知Model
Model基于Intent更新State
View接收到State变化刷新UI。
数据永远在一个环形结构中单向流动,不能反向流动:
示例:
在 Activity / Fragment 这种第一层级的视图中,定义触发 Intent 的逻辑,一般是通过点击事件等操作,和 MVVM 中的触发逻辑一样,不过这里要在协程中触发,并且使用 Channel 或其它 Flow 工具,因为这样做是一种响应式编程的逻辑。
// 新闻界面所有的用户操作, 基本类
class MainActivity : AppCompatActivity() {
// 新闻界面所有的用户操作, 基本类
sealed class NewsIntent
class UserClickNewsIntent(val url: String): NewsIntent() // 用户点击具体某个新闻Item的交互
object RefreshNewsIntent : NewsIntent() // 用户刷新新闻列表的交互
private val viewModel: NewsViewModel = ViewModelProvider(this)[NewsViewModel::class.java]
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initListener()
observerUiState()
}
private fun initListener() {
findViewById<Button>(R.id.refreshButton).setOnClickListener {
sendIntent(RefreshNewsIntent)
}
}
private fun observerUiState() {
CoroutineScope(Dispatchers.Default).launch {
viewModel.newsUiState.collect {
when(it) {
is NewsUiStateInitial -> {
}
is Loading -> {
}
is LoadingSuccess -> {
Toast.makeText(applicationContext, "加载数据成功", Toast.LENGTH_SHORT).show()
}
is LoadingFail -> {
}
}
}
}
}
private fun sendIntent(intent: NewsIntent) {
CoroutineScope(Dispatchers.Default).launch {
viewModel.newsIntent.send(intent)
}
}
}
并且在 VieawModel 中定义数据流的开端:
class NewsViewModel : ViewModel(){
// 定义接收意图的通道
val newsIntent = Channel<MainActivity.NewsIntent>()
private val _newsUiState = MutableStateFlow<NewsUiState>(NewsUiStateInitial)
val newsUiState: StateFlow<NewsUiState> = _newsUiState
init {
viewModelScope.launch {
handleIntent()
}
}
private suspend fun handleIntent() {
newsIntent.consumeAsFlow().collect {
when (it) {
is MainActivity.UserClickNewsIntent -> intoNewsItem(it.url) // 处理 UserClickNewsIntent 意图
is MainActivity.RefreshNewsIntent -> fetchNews() // 处理 RefreshNewsIntent 意图
}
}
}
private fun intoNewsItem(url: String) {
}
private fun fetchNews() {
//模拟耗时操作并获取到数据
_newsUiState.value = LoadingSuccess(ArrayList<NewsItem>())
}
}
定义 UiState 作为 View 的唯一数据源
我们通过归纳新闻页的页面状态,可以分成 初始态、加载中、加载成功、加载失败 四个状态,那么我们将状态收归到 UiState 中去,使用 功能名+UiState 来命名:
sealed class NewsUiState
object NewsUiStateInitial: NewsUiState() // 初始状态
object Loading: NewsUiState() // 正在加载
class LoadingSuccess(val newsList: List<NewsItem>): NewsUiState() // 加载成功
class LoadingFail(val errorMessage: String): NewsUiState() // 加载失败
ViewModel 持有 NewsUiState,并暴露出去,类似于 LiveData 那样子 :
Activity 依赖 ViewModel 持有的 UiState, 用于进行视图刷新:
CoroutineScope(Dispatchers.Default).launch {
viewModel.newsUiState.collect {
when(it) {
is NewsUiStateInitial -> { }
is Loading -> { }
is LoadingSuccess -> {
Toast.makeText(applicationContext, "加载数据成功", Toast.LENGTH_SHORT).show()
}
is LoadingFail -> { }
}
}
}
参考:
https://blog.csdn.net/rikkatheworld/article/details/126472940
https://github.com/HuJianChong/MyWanAndroid
网友评论