Should I use `deriveStateOf` to calculate `statist...
# compose
f
Should I use
deriveStateOf
to calculate
statisticsFoTask
or is that not the correct approach?
Copy code
@Composable
private fun TaskWithStatisticsList(
    tasks: List<Task>,
    taskStatistics: List<TaskStatistic>,
    onTaskDetailsClicked: (Task) -> Unit,
    modifier: Modifier = Modifier
) {
    LazyColumn(
        contentPadding = PaddingValues(bottom = 50.dp),
        modifier = modifier
    ) {
        items(tasks) { task ->
            val statisticsForTask = taskStatistics.filter { it.taskId == task.id }
            TaskWithStatisticsItem(
                task = task,
                statistics = statisticsForTask,
                onTaskDetailsClicked = onTaskDetailsClicked
            )
            Divider()
        }
    }
}
d
Probably not, calculating whether the value has changed is just as expensive as the filter.
f
@Dominaezzz Really? Filtering sounds relatively expensive
The example in the docs looks relative similar but it's not inside the LazyColumn: https://developer.android.com/jetpack/compose/side-effects#derivedstateof
d
Idk, best to measure. I don't typically bother with this sort of caching.
z
How often do you expect this composable to recompose without needing to recalculate the filtered task lists? If that number is small, it doesn’t matter. If you do need caching, remember would be a better fit here - if you’re just passing the value down to another composable, derived state does nothing for you. Also, this almost seems like something that maybe should be done in your view model/presentation layer and not the view layer.
5
f
@Zach Klippenstein (he/him) [MOD] Why does
derivedState
do nothing here? I thought it would avoid filtering the list on every recomposition?
and i am using
remember
together with
derivedStateOf
although I agree that this should probably be done in the ViewModel
I guess with a TaskWithStatistics class
z
DerivedStateOf is for when you are reading snapshot state in order to calculate a value. In your case, you’re not reading snapshot state, you’re just using function arguments. So the only way to new values is for the caller to invoke your function again anyway.
f
As far as I understand, recomposition can happen even if the
tasks
list didn't change. And
derivedStateOf
avoids that the
filter
is executed again in those cases
z
You don’t need
derivedStateOf
for that, you can just use remember:
Copy code
val statisticsForTask = remember(task, taskStatistics) { taskStatistics.filter { it.taskId == task.id } }
That will ensure that the
filter
operation is only performed when either
task
or
taskStatistics
actually change. Compare with:
Copy code
val statisticsForTask = remember { taskStatistics.filter { it.taskId == task.id } }
This won’t work because there’s no keys to
remember
– the initial values of
task
and
taskStatistics
are captured the first time the
remember
is executed, and if it’s executed again with different values, the remembered result won’t get updated. Or this:
Copy code
val statisticsForTask by remember { derivedStateOf { taskStatistics.filter { it.taskId == task.id } }
This also won’t work for the same reason – because there’s no keys to
remember
, and because
taskStatistics
and
task
aren’t snapshot states, just regular values, if they change values the filter will not be recomputed. To make this work with
derivedStateOf
, you’d need to either add keys to
remember
, in which case it’s exactly the same thing as my first example, but with extra overhead that’s not actually adding any value, or you’d need to use
rememberUpdatedState
to convert
taskStatistics
and
task
into snapshot state objects so that
derivedStateOf
can get change notifications. However, in that last case, because the only way those values can change is if your function is being re-executed, there’s absolutely no benefit to using `rememberUpdatedState`+`derivedStateOf` . Just the simple keyed remember is good enough.
c
so
LaunchedEffect(task, taskStatistics) { ..}
can be a good choice here, since filter can be a "heavy computation"
f
@Zach Klippenstein (he/him) [MOD] Thank you very much for the explanation 🙏
@Zach Klippenstein (he/him) [MOD] so is the example in the docs point as well? Couldn't they use a normal remember here? https://developer.android.com/jetpack/compose/side-effects#derivedstateof
oh now I get the difference, in the docs the value it derives its state from is itself a State object
z
Yes exactly. If you’re not reading a snapshot state object,
derivedStateOf
doesn’t do anything interesting.
@carbaj0 not necessarily. 1. Operations on lists that aren’t enormous are actually pretty fast 2. If you perform the operation asynchronously, you will have to also deal with empty/null initial state and if the operation is really fast, where the data is already available on the second frame, then it will probably just look like your UI is buggy because that null state will show for a single frame then disappear (ie it will look like a flicker). But if the list is big, or the filter operation is extremely expensive, then these are problems you might need to solve. This is also why this should be in the view model instead of the composable, if this logic gets more complex it’s easier to test and stuff in the view model layer.
👍 1
f
I have that flicker you mention in many different places where I load data asynchronously. I can't really avoid that I think
z
Right, but this is not one of those cases, given the code you shared.
When you really do need asynchronous loading, there are other techniques you can use to get rid of that flicker – e.g. enforce a minimum time to display some loading UI (yes actually makes your app technically slower, but seeing an actual brief loading screen is often a more polished experience than flickering).
f
yea that makes sense
do you base that delay on the last piece of data that has loaded or do you just estimate a duration?
z
I think there are studies about what duration is most satisfying, but I can't remember what the good numbers are. Something long enough that you can register the loading is occurring though, accounting for transition animations, etc
f
ok thank you