Brady Aiello
12/11/2020, 5:05 AMobserveAsState()
and Flow's collectAsState()
just not being invoked on change anymore, where observe()
and collect()
lambdas are still being invoked on change.
ViewModel:
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:
// 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()
.allan.conda
12/11/2020, 7:21 AMZach Klippenstein (he/him) [MOD]
12/11/2020, 3:56 PMBrady Aiello
12/11/2020, 5:39 PMval 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))
Zach Klippenstein (he/him) [MOD]
12/11/2020, 5:47 PMBrady Aiello
12/11/2020, 6:04 PMval 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
@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.Zach Klippenstein (he/him) [MOD]
12/11/2020, 6:09 PMcollectAsState
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.Zach Klippenstein (he/him) [MOD]
12/11/2020, 6:10 PMBrady Aiello
12/11/2020, 6:15 PMBrady Aiello
12/11/2020, 6:16 PM...already behaves as ifoperator is applied to it,distinctUntilChanged
Brady Aiello
12/11/2020, 6:18 PMFlow
by writing a produceState
to take a SnapshotMutationPolicy
, it won't be possible with StateFlow
because it goes against one of its fundamental properties.Zach Klippenstein (he/him) [MOD]
12/11/2020, 7:37 PM