基类:
//通知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){
}
}
}
}
大概实现的代码就是这些,运行起来看没太大毛病,有问题欢迎探讨!