Android架构 - MVC、MVP、MVVM、MVI

一、概念

MVC Activity类过于臃肿
MVP Presenter不仅要操作数据还要更新View
MVVM
MVI

二、MVC(Model-View-Controller)

三、MVP(Model-View-Presenter)

四、MVVM(Model-View-ViewModel)

五、MVI(Model-View-Intent)

模型 Model 负责处理数据的状态和逻辑。
视图 View 负责展示界面和数据的状态。
意图 Intent 代表用户的操作(点击、输入、获取列表数据等)。
状态 State

反应数据当前值。

5.1 唯一可信数据源

为了解决 MVVM 中 UI 订阅多个分散的 State(ViewModel中的LiveData/Flow)导致各种数据并行更新或数据相互依赖时,无法清晰掌握整个页面的状态。MVI使用 UiState 将所有 State 整合在一处,UI刷新只依赖这一个数据源。

5.2 数据单向流动

DataBinding  数据模型和视图一方发生变化就会同步到另一方,数据的流动是双向的,这样不便于追踪测试。MVI强调数据的源头只有一个,目的地也只有一个。数据从 Data Layer 流向 UI Layer 的 ViewModel 中,ViewModel 将数据转换成 State 传输给 Element 更新。

5.3 搭建项目(Compose版)

5.3.1 定义状态 State

  • 命名采用:功能+UiState。
  • 使用 data class 因为自带 copy() 功能,非常方便更新部分属性。界面刷新用到的状态全都定义成属性,集中在一起实现唯一可信数据源。
  • 一般同 ViewModel 写在同一个 .kt 文件中,也可以单独写在一个 .kt 文件中。
data class NewsUiState(
    val isLoading: Boolean = false,
    val success: List<HotNewestTopBean.HotNewestTop> = emptyList(),
    val fail: String = ""
)

5.3.2 定义事件 Event

  • 命名采用:场景+Event。
  • 使用可能的用户行为全部定义成子类。
  • 子类不带参数就定义成 object 方便复用(因为创建的实例无状态区别)。
  • 一般同 ViewModel 写在同一个 .kt 文件中,也可以单独写在一个 .kt 文件中。
sealed interface NewsEvent {
    //条目点击
    data class NewsItemClick(val url: String) : NewsEvent
    //初始化数据
    object InitData: NewsEvent
}

5.3.3 处理意图→更新状态 ViewModel

  • Compose 向外部发送 Intent 属于副作用,因此是在协程中发送,跨协程通信选择 Channel 因为它并发安全(默认模式 RENDEZVOUS)。
class DemoViewModel(
    private val repository: NewsRepository
) : ViewModel() {
    //暴露给UI订阅State
    var newsUiState by mutableStateOf(NewsUiState())
        private set

    //定义发送Event的Channel
    private val eventChannel = Channel<NewsEvent>(Channel.UNLIMITED)

    //初始化时就启动Event处理
    init { handleEvent() }

    //处理Event
    private fun handleEvent() {
        viewModelScope.launch {
            eventChannel.consumeEach { newsEvent ->
                when(newsEvent) {
                    is NewsEvent.NewsItemClick -> getNews()
                    NewsEvent.InitData -> getNews()
                }
            }
        }
    }

    //暴露给UI发送Event(比直接在UI中获取Channel发送方便)
    fun sendEvent(event: NewsEvent) {
        viewModelScope.launch {
            eventChannel.send(event)
        }
    }

    //(在业务代码里)更新State
    private suspend fun getNews() {
        newsUiState = newsUiState.copy(isLoading = true) //状态设为加载中
        runCatching {
            repository.getNewsData()
        }.onSuccess { response ->
            response.getData().onSuccess {
                newsUiState = newsUiState.copy(success = it)  //状态设为成功
            }.onFailure {//网络的错误
                newsUiState = newsUiState.copy(fail = it.message.toString())  //状态设为失败
            }
        }.onFailure { //协程的错误
            newsUiState = newsUiState.copy(fail = it.message.toString())  //状态设为失败
        }
    }

    override fun onCleared() {
        super.onCleared()
        eventChannel.close()  //释放资源
    }
}

5.3.4 处理状态→发送意图 UI

Activity {

}

5.4 搭建项目(View版)

定义状态和意图同上。

5.4.1 处理意图→更新状态 ViewModel

ViewModel {
    //将UiState暴露出去
    val newsUiState = mutableStateOf<NewsUiState>(NewsUiState.Init)

    //处理Intent(去调用业务代码)
    fun dispatch(intent: NewsIntent) {
        viewModelScope.launch {
            when(intent) {
                is NewsIntent.NewsItemClick -> getNews()
            }
        }
    }

    //(在业务代码里)更新State
    private suspend fun getNews() {
        newsUiState.value = NewsUiState.Loading //状态设为加载中
        runCatching {
            repository.getNewsData()
        }.onSuccess { response ->
            response.getData().onSuccess {
                newsUiState.value = NewsUiState.Success(it)  //状态设为成功
            }.onFailure {   //API错误
                newsUiState.value = NewsUiState.Fail(it.message.toString())  //状态设为失败
            }
        }.onFailure {   //协程错误
            newsUiState.value = NewsUiState.Fail(it.message.toString())  //状态设为失败
        }
    }

}

5.4.2 处理状态→发送意图 UI

Activity {
    onCreate {
        initView()
        observeUiState()
    }

    private fun initView() {
        binding.btn.setOnClickListener {
            viewModel.dispatch(NewsIntent.RefreshNewsList)
        }
    }
    private fun observeUiState() {
        lifecycleScope.launch {
            lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
                val state = viewModel.newsUiState.value
                when(state ) {
                    NewsUiState.Init -> TODO()
                    NewsUiState.Loading -> TODO()
                    is NewsUiState.Success -> {adapter.setData(state.list)}
                    is NewsUiState.Faild-> TODO()
                }
            }
        }
    }
}

猜你喜欢

转载自blog.csdn.net/HugMua/article/details/131458807