Android MVI学习笔记

149 阅读2分钟

1 MVI简述

2 架构演变

实验场景:实现一个简单页面

  1. 进入页面加载Loading
  2. 请求用户名和一个简单列表,列表数据有标题,内容和勾选。请求返回后更新UI用户名和列表数据并取消Loading
  3. 点击列表某一行时,反选勾选框

2.1 MVVM

// bean
data class UserNewsBean(
    var title: String = "",
    var content: String = "",
    var isCheck: Boolean = false
)

// Model层
class UserPageRepository {
    
    suspend fun getUserName(): String {
        delay(1000)
        return "xxx"
    }
    
    suspend fun requestUserNewsList(): List<UserNewsBean> {
        delay(2000)
        val dataList = mutableListOf<UserNewsBean>()
        for (i in 0 until 20) {
            dataList.add(UserNewsBean(
                "title$i",
                "content$i",
                false
            ))
        }
        return dataList
    }
}

// ViewModel层
class UserPageViewModel: BaseViewModel() {
    // --model start--
    private val _showLoading = MutableLiveData<Boolean>()
    val showLoading: LiveData<Boolean> = _showLoading
    
    private val _userName = MutableLiveData<String>()
    val userName: LiveData<String> = _userName
    
    private val _dataList = MutableLiveData<List<UserNewsBean>>()
    val dataList: LiveData<List<UserNewsBean>> = _dataList
    // --model end--
    
    private val repository = UserPageRepository()
    
    fun attach() {
        viewModelScope.launch {
            _showLoading.value = true
            _userName.value = repository.getUserName()
            _dataList.value = repository.requestUserNewsList()
            _showLoading.value = false
        }
    }
    
    fun onItemClick(position) {
        val dataListTemp = _dataList.value
        if (!dataListTemp.isNullOrEmpty && position in dataListTemp.indices) {
            dataListTemp[position].isCheck = !dataListTemp[position].isCheck
            _dataList.value = dataListTemp
        }
    }
}

// View层
class UserPage: BaseActivity() {
    private val viewModel = createViewModel()
    
    fun onCreate() {
        viewModel.showLoading.observe(this) {
            // progressBar show or hide
        }
        
        viewModel.userName.observe(this) {
            // update userName
        }
        
        viewModel.dataList.observe(this) {
            // update list
        }
        
        list.setOnItemClickListener { position ->
            viewModel.onItemClick(position)
        }
    }
}

特点:数据从ViewModel单向流向了View层,页面事件从View层流向了ViewModel层

2.2 Flow

class UserPageViewModel: BaseViewModel() {
    // --model start--
    private val _showLoading = MutableStateFlow<Boolean>()
    val showLoading: StateFlow<Boolean.Companion> = _showLoading.asStateFlow()
}

class UserPage: BaseActivity() {
    fun onCreate() {
        lifecycleScope.launch {
            launch {
                viewModel.showLoading.collect {
                }
            }
            launch {
                viewModel.userName.collect {
                }
            }
            launch {
                viewModel.dataList.collect {
                }
            }
        }
    }
}

2.3 MVI

3 踩坑

3.1 StateFlow更新list,第一次可以收到更新列表,但是后面list变更后通知收不到

private val _uiState:StateFlow<UserPageUiState>()
val uiState: StateFlow<UserPageUiState> = _uiState.asStateFlow()

//1.第一次变更,view可以收到通知并更新列表
dataList = requestNews()
_uiState.value = UserPageUiState(false, "name", dataList)

//2.第二次,收不到了,下面的方法都试了都不行,StateFlow在value变更时,会判断oldValue == newValue就啥都不干
_uiState.value.dataList[position].isCheck = !_uiState.value.dataList[position].isCheck
//_uiState.value = _uiState
//_uiState.value = UserPageUiState(false, "name", _uiState.value.dataList)
//_uiState.value = UserPageUiState(false, "name", new List addAll(_uiState.value.dataList))

//3.修改列表,使用复制的方式就可以了
val dataList = _uiState.value.dataList.toMutableList()
dataList[position] = dataList[position].copy(isCheck = !dataList[position].isCheck)
_uiState.value = _uiState.value.copy(dataList = dataList)

3.2 观察多个值变化时,collect只有第一个收到消息

// view层
lifycycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.uiState.apply {
            this.map { it.showLoading }
                .distinctUntilChanged()
                .collect {
                    // do something
                }
            this.map { it.userName }
                .distinctUntilChanged()
                .collect {
                    // do something
                }
            this.map { it.dataList }
                .distinctUntilChanged()
                .collect {
                    // do something
                }
        }
    }
}

因为collect是阻塞的,只有第一个showLoading变化了能接收到,所以需要新开协程去接收每个值的变化

// view层
lifycycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.uiState.apply {
            launch {
                this.map { it.showLoading }
                .distinctUntilChanged()
                .collect {
                    // do something
                }
            }
            launch {
                this.map { it.userName }
                .distinctUntilChanged()
                .collect {
                    // do something
                }
            }
            launch {
                this.map { it.dataList }
                .distinctUntilChanged()
                .collect {
                    // do something
                }
            }
        }
    }
}

后面加了个扩展方法

fun<T, R> StateFlow<T>.setObserver(coroutineContext: CoroutineContext, dataGet: (T) -> R, action: (R) -> Unit) {
    coroutineContext.launch {
        this@setObserver.map { dataGet.invoke(it) }
                        .distinctUntilChanged()
                        .collect {
                            action.invoke(it)
                        }
    }
}

// view层
lifycycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.uiState.apply {
            setObserver(this@repeatOnLifecycle, { this.value.showLoading }) {
                // do something
            }
            setObserver(this@repeatOnLifecycle, { this.value.userName }) {
                // do something
            }
            setObserver(this@repeatOnLifecycle, { this.value.dataList }) {
                // do something
            }
        }
    }
}

3.3 StateFlow连续发送多个状态时,中间几个状态丢失了

特性。。。

参考

# 安卓MVI架构真的来了?动手试着封装吧(一)