One Off the Slack: Is It Necessary to Remember?
Sometimes, these Slack threads get a bit long… though, in this case, there was a fair amount of discussion about young children and their impacts on early-morning work.
Bradleycorn had this composable:
@Composable
fun FancyText(text: String) {
val formattedText = remember(text) { computeTextFormatting(text) }
Text(formattedText)
}
His question was:
Is the use of
remembernecessary/valuable here? Seems like the only time this would get recomposed is iftextchanges, and in that case we’d want to re-compute theformattedTextvalue. In other words, we’ll always re-compute theformattedTextvalue, so do we even needremember?
Dominic Fisher pointed out that there are other parties involved:
There are ambients in the background that can also change.
Google’s Adam Powell pointed out that remember is not strictly for performance-related caching:
Reach for
rememberif you need the properties of the same object outliving a single composition and retaining its identity across those compositions
remembercan be very useful for performance optimizations via caching, but there are subtleties. For example, it compares keys via.equalsto determine whether it should recompute the value by running the block. Sometimes this can be as expensive or more than just running the operation every time; consider a very deep tree ofdata classes or large collections where each element will be compared
so when it comes to caching for performance using
remember, don’t do it yet, but it’s there if you find you need it.
Dominic then inquired about derivedStateOf():
Can
val formattedText = remember(text) { computeTextFormatting(text) }be replaced withderivedStateOf { computeTextFormatting(text) }?
Google’s Jim Sproch said “no”:
No,
derivedStateOfdoesn’t do any caching; it merely provides a value that subscribes to any [Stateobject] reads and will update its self when a dependency changes. SorememberandderivedStateOfdo orthogonal things.
When pressed for an example of derivedStateOf(), Jim offered:
var a by mutableStateOf(0)
var b by mutableStateOf(0)
val sum = derivedStateOf { a + b }
If either
aorbchanges, then sum will also automatically change. No memory involved, yes updates/subscriptions involved.
Dominic countered:
Why not just do
val sum = a + b?
Presumably, Dominic meant in the context of a composable. Compose is tracking the states, so it will re-run a composable function
where a or b are detected to change. Jim pointed out that this is not the only scenario:
Another important property is that
derivedStateOfcan be used outside of composition to build all sorts of fun data structures that cache computations before passing them into composables.
And Adam pointed out the relationship between these things and snapshots:
in most cases, that’s much simpler… as long as
sumis only read in the local part of the composition. An important property ofderivedStateOfobjects is that they change as part of the same snapshot transaction as their inputs; readers won’t see a state of the world whereaorbchange butsumhasn’t been updated yet. And if those inputs do change, only places wheresumis read will invalidate, not the place where it was computed.
class FancyState {
var a by mutableStateOf(0)
var b by mutableStateOf(0)
val result by derivedStateOf {
somethingVeryExpensiveWith(a, b)
}
}
So if you have [the above
FancyState] class, then the expensive operation will be cached and only run when needed, and conceptually as part of the same snapshot transaction whereaorbchanged
so it’s kind of like an observable
by lazythat can change over time
The thread continues on the next day to look at an updated sample from Bradleycorn and review comments from Jim and Dominic — check out the whole thread to see how it turns out!
Read the original thread in the kotlinlang Slack workspace. Not a member? Join that Slack workspace here!