Compose使用LiveData遇到的问题及适配方案

351 阅读3分钟

在Compose开发中,数据层推荐使用的数据类型是Flow和mutableState,对于简单类型的数据状态,可以直接在ViewModel中使用mutableStateof()或mutableStateListOf()声明。对于比较复杂的数据流处理,则建议通过flow实现,在Compose中调用flow.collectAsStateWithLifecycle()。

对于LiveData,虽然Compose也提供了liveData.observeAsState()的方法,但LiveData本身是有活跃状态周期的,在非活跃状态是接收不到数据变化的,因此在一些情况下会出现UI跟不上数据的问题,明明调用了liveData.postValue()方法,但是在Compose中却监听不到变化。

LiveData生命周期

LiveData的观察者是存在活跃状态跟非活跃状态的,在活跃状态中才能接受到数据的更新。如果处于非活跃状态中,则需要等待状态恢复为活跃,此时依然可以收到最新数据,因为LiveData具有数据倒灌特性。

而LiveData的活跃状态定义为LifeCycle.State.STARTED和LifeCycle.State.RESUMED,对于Activity的生命周期为OnStarted到OnPause之间

Compose的observeAsState分析

在Compsoe中,可以通过LiveData.observeAsState将LiveData转化为mutableState,并且绑定到当前页面的lifecycle。但是,这是怎么实现的呢

查看源码,实现其实很简单

@Composable
fun <T> LiveData<T>.observeAsState(): State<T?> = observeAsState(value)

@Composable
fun <R, T : R> LiveData<T>.observeAsState(initial: R): State<R> {
    val lifecycleOwner = LocalLifecycleOwner.current
    val state = remember {
        @Suppress("UNCHECKED_CAST") /* Initialized values of a LiveData<T> must be a T */
        mutableStateOf(if (isInitialized) value as T else initial)
    }
    DisposableEffect(this, lifecycleOwner) {
        val observer = Observer<T> { state.value = it }
        observe(lifecycleOwner, observer)
        onDispose { removeObserver(observer) }
    }
    return state
}

observeAsState内部维护了一个mutableState类型的变量,通过DisposableEffect实现了对LiveData的注册和移除,使用了一个新的Observer对象,把liveData的值实时同步到mutableState中,而State就是Compose组件可以感应的数据。

如果你不知道DisposableEffect的作用,可以查看深入理解Jetpack Compose中的函数的执行顺序这篇文章,它的作用类似于页面的onCreate和onDestroy周期

问题分析

在Compose Screen中,如果是使用Navigation来作为页面的切换,则在navController.navigate方法后,前Screen会走onDispose的回调。

但如果是另起了一个新的Activity,那么前Screen并不会走onDispose回调。在这种情况下,如果使用了

val value: String by liveData. observeAsState("initial")
Text("Value is $value")

的方式来监听liveData,并且在新的Activity中,对liveData进行数据更新

liveData.postValue("new")

那么当返回前一个页面的时候,发现value的值并没有同步更新到

解决方案

分析observeAsState的源码和liveData机制,当页面恢复到活跃状态时,liveData会将最新值倒灌至观察者,那么数据的变化应该是可以监听到的。

但是Compose内部会对UI进行智能的更新,而observeAsState中的状态是保存在LiveData内部的,因此考虑到了状态提升

状态提升

Compose中建议将状态放在最上层的Compose组件中,基层的Compose组件都以无状态的形式,方便维护和管理

将liveData内的状态提升到Compose中如下:

@Composable
fun Test() {
    val lifecycleOwner = LocalLifecycleOwner.current
    var state by remember {
        mutableStateOf("")
    }
    DisposableEffect(liveData, lifecycleOwner) {
        val observer = Observer<String> {
            state = it
        }
        liveData.observe(lifecycleOwner, observer)
        onDispose { liveData.removeObserver(observer) }
    }
    Text(state)
}

测试后发现状态的更新可以被监测到了

通过ViewModel监听liveData

在viewModel中手动管理liveData的绑定和解除,由于viewModel中没有实现lifecycle,因此在init和onCleared()方法中处理

var state by mutableStateOf("")

private val observer = Observer<String> {
    state = it
}

init {
    livedata.observeForever(observer)
}

override fun onCleared() {
    livedata.removeObserver(observer)
}

在Compose中,通过state获取数据最新值