Compose实战-以MVI的方式写Compose

一.几种常见架构模式简介

MVC架构:

M指的是Model,数据模型;

V指的是界面,一般是xml布局,或者有些界面我们直接使用代码书写的,所以也包含Activity和Fragment;

C指的是Controller,控制逻辑,一般控制逻辑都在Activity,Fragment中。

优点:

把逻辑拆分开,避免耦合。

缺点:

我们通过上面的简单介绍就可以看到一些问题,首先Activity,Fragment担当的职责过多,违背职责单一原则。实际应用中,MVC架构下的Activity,Fragment代码往往几千行,极其臃肿。

MVP架构:

M指的是Model,还是数据模型;

V指的是IView,视图逻辑接口,而不是单纯的视图了;

P指的Presenter,向导交流中心,处理核心逻辑。

我们也总结一下MVP的优点和缺点:

优点:

1.完全的解耦。其原因是我们是面向接口编程的,职责十分清晰,属于设计上的依赖倒置。

2.Presenter和Model是可以复用的。单纯的界面变化,我们只需要修改IView的实现即可。

3.由于相互之间是完全解耦的,所以可以针对M,V,P分开单独测试。

4.三者之间相互不耦合,所以方便后续扩展和升级。

缺点:

1.面向接口编程,所以各种界面渲染场景比较多的时候,会造成接口爆炸的问题。

2.接口升级时,不单单要修改实现类,有时甚至需要修改接口。

MVVM架构:

M指的是Model,仍然是数据模型;

V指的是View,视图;

VM指得是ViewModel,属于关联视图和Model的。View和Model相互不关联,通过ViewModel进行双向绑定。修改Model后,View则对应发生变化。

优点:

1.代码书写上的简洁。因为代码中少了setText这样的代码。

2.职责的清晰。相关的业务逻辑都在每个ViewModel中,不同的ViewModel承担不同的了逻辑。

3.针对数据的改变会自动反馈到界面上,使用起来会方便的多。

缺点:

缺点这块也是个人的见解。

1.因为数据数双向自动绑定的,所以监控到底是哪个ViewModel最终去修改的text值变的困难。当然,可以通过监控数据的改变来解决这问题。

小结:

MVC和MVP都属于命令式的UI,所以都需要找到一个需要设置的对象,比如setText,setBackground等等。

而MVVM和MVI属于声明式UI,只需要修改对应的参数,而不需要给到被设置的对象,所以在前端交互上,会让我们感觉到更便捷。

二.MVI架构

MVI架构是面向意图编程,我们把各种诉求转化为一个个的意图,请求列表数据是一个意图,请求详细信息也是一个意图。

M:ViewModel,指的是可以被观测数据;

V:View,指的是视图;

I:Intent,指的是意图,比如获取列表数据,获取详情搜索数据等。

具体流程我们可以用下面这张图来概括:

三.使用MVI架构来写Compose

接下来,我们会按照上面图中的顺序,一步一步的来使用MVI框架编写一个完整的界面流程。

首先.我们先引入Compose框架。创建MVIComposeActivity。

ContentViewModel的话不着急,这一步只要先创建一个ContentViewModel的类即可。

class MVIComposeActivity : ComponentActivity() {
    private val viewModel by viewModels<ContentViewModel>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ContentView(viewModel)
        }
    }
}

接下来,我们就要开始构建我们的意图了。

ContentViewModel中,构建我们的意图,目前先定义两个,获取列表和获取详情。

sealed class ContentIntent {
    //传递消息
    object GetContent : ContentIntent()
    class GetItemDetail(val select: Int) : ContentIntent()
}

这里肯定有人会问,为什么一个是object类型,一个class类型。因为第二个是带参数的,所以每次都需要重新构建一个意图,而第一个无参,则复用同一个就可以了。

第三步,我们来构建ViewModel

创建信道newsChannel。信道来接受各种用户行为发过来的意图。

创建状态管理uiState,由其完成数据的绑定。

最后在初始化的时候,进行意图和获取数据的关系绑定(handleIntent方法)。

class ContentViewModel : ViewModel() {
    //Channel信道,意图发送别ViewModel,
    val newsChannel = Channel<ContentIntent>(Channel.UNLIMITED)

    //状态管理
    var uiState by mutableStateOf(NaviViewState())

    init {
        handleIntent()
    }

    private fun handleIntent() {
        viewModelScope.launch {
            newsChannel.consumeAsFlow().collect() {
                when (it) {
                    is ContentIntent.GetContent -> getContent()
                    is ContentIntent.GetItemDetail -> getNewsDetail(it);
                }
            }
        }
    }

    private val newsFlow: Flow<List<String>> = flow {
        val list = mutableListOf<String>()
        list.add("111")
        list.add("222")
        emit(list)
    }

    private val detailFlow: Flow<List<String>> = flow {
        val list = mutableListOf<String>()
        list.add("111-详情")
        list.add("222-详情")
        emit(list)
    }

    private fun getContent() {
        viewModelScope.launch {
            newsFlow.flowOn(Dispatchers.Default).collect { contents ->
                uiState = uiState.copy(dataList = contents)
            }
        }
    }

    private fun getNewsDetail(select: ContentIntent.GetItemDetail) {
        viewModelScope.launch {
            detailFlow.flowOn(Dispatchers.Default).collect { contents ->
                uiState = uiState.copy(detailContent = contents[select.select])
            }
        }
    }
}

第四步,回过头来,我们进入到View的流程。

进入到页面后,首先进行的意图是请求所有数据。

@Composable
fun ContentView(viewModel: ContentViewModel) {
    //请求数据
    val viewState = viewModel.uiState
    //请求数据的意图
    LaunchedEffect(key1 = true) {
        viewModel.newsChannel.send(ContentIntent.GetContent)
    }
    //数据与View的绑定
    Column(
        Modifier
            .fillMaxSize()
            .verticalScroll(rememberScrollState())
    ) {
        viewState.dataList!!.forEachIndexed { index, it ->
            NewsItem(viewModel, position = index, news = it)
        }
    }
    viewState.detailContent?.let {
        ToastUtil.showCenterToast(it)
    }
}

则此时我们上一步中构造的handleIntent方法中的newsChannel就会收到这个意图,并进行对应的处理:

private fun handleIntent() {
        viewModelScope.launch {
            newsChannel.consumeAsFlow().collect {
                when (it) {
                    is ContentIntent.GetContent -> getContent()//这里进行处理
                    is ContentIntent.GetItemDetail -> getNewsDetail(it);
                }
            }
        }
    }

getContent方法中去请求数据(这里可以进行网络请求,我这里就不进行了),返回结果后,把数据绑定到uiState上。

 private fun getContent() {
        viewModelScope.launch {
            newsFlow.flowOn(Dispatchers.Default).collect { contents ->
                uiState = uiState.copy(dataList = contents)
            }
        }
    }

至此,流程上就结束了。MVI架构下,我们只需要修改uiState就可以了。

完整代码地址:

android_all_demo/MVIComposeActivity.kt at master · aa5279aa/android_all_demo · GitHub

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

失落夏天

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值