MVI框架尝试封装

基类:

//通知UI
@Keep
interface IUiEvent
//用户动作
@Keep
interface IUiIntent
//UI状态
@Keep
interface IUiState
//ViewModel
abstract class IViewModel<UiState : IUiState , UiEvent : IUiEvent, UiIntent : IUiIntent> : ViewModel(){
    //接收用户输入事件
    private val uiIntent = Channel<UiIntent>(Channel.UNLIMITED)
    //一次性UI事件
    private val uiEvent by lazy { MutableSharedFlow<UiEvent>() }
    //对内提供
    private val _uiState by lazy {
        val iState = initUiState()
        if(iState == null) null else MutableStateFlow(iState)
    }
    //对外提供
    val uiState by lazy { _uiState!!.asStateFlow() }

    //协程接收用户输入事件
    init {
        viewModelScope.launch {
            uiIntent
                .receiveAsFlow()
                .collect{ intent->
                    handleIntent(intent)
                }
        }
    }

    //根据页面需求,是否创建UI状态
    protected open fun initUiState():UiState?{
        return null
    }

    //用户输入事件处理具体实现
    protected abstract fun handleIntent(intent: IUiIntent)

    //发送UI一次性事件
    protected suspend fun sendUiEvent(event: UiEvent){
        uiEvent.emit(ac)
    }

    //更新View状态
    protected fun updateUiState(copy : UiState.() -> UiState){
        _uiState?.let { ui->
            ui.update {
                copy(ui.value)
            }
        }
    }

    //供页面调用发送用户事件方法
    fun sendUiIntent(intent: UiIntent){
        viewModelScope.launch {
            uiIntent.send(intent)
        }
    }

    //供页面调用,接收用户事件处理完后的响应数据
    fun handleUiEvent(onUiEvent : (uiEvent : UiEvent) -> Unit){
        //distinctUntilChanged 去重
        viewModelScope.launch {
            uiEvent.collect{
                onUiEvent(it)
            }
        }
    }

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

简单实现:

//UiEvent实现类
sealed class MainUiEvent: IUiEvent{
    //通知UI数据处理结果
    data object OnLocalAudioResult : MainUiEvent()
    //通知UI需要动态申请权限
    data class RequestAudioPermission(val permission : String) : MainUiEvent()
}
//UIIntent实现类
sealed class MainUiIntent : IUiIntent {
    //用户获取本地音频文件的事件
    data object GetLocalAudio : MainUiIntent()
}
//UIState实现类
data class MainUiState : IUiState{
    //保存音频数量
    val musicCount : Int = 0
}
//ViewModel实现类
class MainViewModel : IViewModel<MainUiState, MainUiEvent, MainUiIntent>() {

    override fun initUiState(): MainUiState {
        return MainUiState()
    }

    override fun handleIntent(intent: IUiIntent) {
        viewModelScope.launch {
            when(intent){
                //响应用户意图
                is MainUiIntent.GetLocalAudio -> {
                    //判断权限
                    if (PermissionManager.hasAudioPermission()) {
                        //如果没有缓存,重新拉取数据
                        if (LocalMediaManager.localAudioList.isEmpty()) {
                            LocalMediaManager.getLocalAudioList()
                            //通知UI数据处理结果
                            sendUiEvent(MainUiEvent.OnLocalAudioResult)
                        }
                        //保存数据总条数,这里使用copy的原因是可以单独更新一个数据项,其他数据从原来的
                        //数据上拷贝过来,如果需要使用全新的数据,则可以传入一个新的MainUiState
                        updateUiState { copy(musicCount =  LocalMediaManager.localAudioList.size) }
                    } else { 
                        //通知UI需要动态获取权限
                        sendUiEvent(MainUiEvent.RequestAudioPermission(PermissionManager.getAudioPermission()))
                    }
                }
                //响应其他用户事件
                else ->{}
            }
        }
    }
}
//页面
@Composable
fun MusicPage(modifier: Modifier = Modifier) {

    val vm : MainViewModel = viewModel()
    val uiState = vm.uiState.collectAsStateWithLifecycle()
    //权限请求
    val launcher = rememberLauncherForActivityResult(contract = ActivityResultContracts.RequestPermission()) {
        if(it){
            vm.sendUiIntent(MainUiIntent.GetLocalAudio)
        }
    }
    
    LaunchedEffect(Unit) {
        //UIEvent事件接收,如果vm里面用的挂起,而不是另起协程,则需要考虑把这个方法单独放在一个协程里,
        //否则会导致,一直堵塞在这个方法
        vm.handleUiEvent {
            when(it){
                is MainUiEvent.RequestAudioPermission->{
                    launcher.launch(it.permission)
                }
                else ->{}
            }
        }
       //发送用户意图,这里直接页面初始化就请求
        vm.sendUiIntent(MainUiIntent.GetLocalAudio)
    }
    //简单的显示本地音频的数量
    LazyColumn(modifier.fillMaxWidth()) {
        item {
            MusicTabItem(R.drawable.ic_local_music , R.string.local_music , uiState.value.musicCount){

            }
        }
    }
}

大概实现的代码就是这些,运行起来看没太大毛病,有问题欢迎探讨!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容