I'm having an issue with LiveData's `observeAsSta...
# compose
b
I'm having an issue with LiveData's
observeAsState()
and Flow's
collectAsState()
just not being invoked on change anymore, where
observe()
and
collect()
lambdas are still being invoked on change. ViewModel:
Copy code
private val _spellLiveData: MutableLiveData<DataState<List<Spell>>> = 
                MutableLiveData(DataState.Loading)
val spellLiveData: LiveData<DataState<List<Spell>>> = _spellLiveData
...
// Posting initial Loading, Success states
viewModelScope.launch {
   ...
    _spellLiveData.postValue(it)
}
...
// Posting new Success states with modified data
viewModelScope.launch {
   ...
    _spellLiveData.postValue(it)
}
MainActivity:
Copy code
// This onChanged listener will always be invoked after a change.
viewModel.spellLiveData.observe(owner = this, onChanged = {
    Log.d(TAG, "onCreate: $it")
})

// This will stop being invoked after the initial Loading, then Success states.
// So we can't update it.
val spells by viewModel.spellLiveData.observeAsState(initial = DataState.Loading)
I'm fetching 308 DnD 5e Spells, each with a few paragraphs about how they're used, as well as some smaller fields. After posting the initial 308, I can't post another 308 in its place and have it update. But, when I post modified data with just 3 spells,
observeAsState()
and
collectAsState()
work fine, and the views recompose correctly. So I'm wondering if I've reached the limits of what state can do? I thought it might be a bug in
observeAsState()
, but the same problem occurs with Flow's
collectAsState()
.
a
maybe it’s just slow?
z
Are you re-posting the same instance of a MutableList by any chance?
b
I thought that might be an issue, so I'm copying the data to a new list.
Copy code
val spells = dataState.data.toMutableList()
val spell: SpellInMemory = spells.find { it.name == name }!!
spell.expanded = !spell.expanded
val newList: MutableList<SpellInMemory> = mutableListOf()
newList.addAll(spells)
_spellLiveData.postValue(DataState.Success(newList))
z
mutableStateOf takes a “snapshot mutation policy” param. It’s structural by default. What if you change it to referential? See https://developer.android.com/reference/kotlin/androidx/compose/runtime/SnapshotMutationPolicy
b
Good find! Thank you! So, I can get it to work with this:
Copy code
val liveDataSpellsByState = remember {
    mutableStateOf<DataState<List<SpellInMemory>>>(DataState.Loading, referentialEqualityPolicy())
}

// This onChanged listener will always be invoked after a change.
viewModel.spellLiveData.observe(owner = this, onChanged = {
    liveDataSpellsByState.value = it
})
but I can't use the simple
observeAsState()
or
collectAsState()
if using the default
structuralEqualityPolicy()
, and the 2 data structures take a long time to determine structural equality. It'd be nice if we could pass the equality policy as a param like
Copy code
@Composable
fun <R, T : R> LiveData<T>.observeAsState(initial: R, 
                                          snapshotMutationPolicy: SnapshotMutationPolicy = structuralEqualityPolicy()): State<R> {
    val lifecycleOwner = LifecycleOwnerAmbient.current
    val state = remember { mutableStateOf(initial, snapshotMutationPolicy) }
    onCommit(this, lifecycleOwner) {
        val observer = Observer<T> { state.value = it }
        observe(lifecycleOwner, observer)
        onDispose { removeObserver(observer) }
    }
    return state
}
Might add something like this for
Flow
and
LiveData
to my utils extension functions.
z
I’m guessing
collectAsState
doesn’t have a policy param because Flow itself is pretty opinionated and only supports structural equality in most cases (eg. state flow, conflated, distinctUntilChanged(?)), so it just wouldn’t work if any of those operators were used upstream.
Anyhoo, probably worth filing a feature request to add the policy to those operators and give your use case (expensive structural equality computation) instead of just assuming it’s missing for a good reason.
b
Hmmm...interesting! Never put in a feature request, but I may!
Also, I was originally using StateFlow, which
...already behaves as if
distinctUntilChanged
operator is applied to it,
So, though this option may be possible with
Flow
by writing a
produceState
to take a
SnapshotMutationPolicy
, it won't be possible with
StateFlow
because it goes against one of its fundamental properties.
z
Yea, I think your only option there would be to write a custom equals method on your data holder type.
👍 1
215 Views