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
remember
necessary/valuable here? Seems like the only time this would get recomposed is iftext
changes, and in that case we’d want to re-compute theformattedText
value. In other words, we’ll always re-compute theformattedText
value, 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
remember
if you need the properties of the same object outliving a single composition and retaining its identity across those compositions
remember
can be very useful for performance optimizations via caching, but there are subtleties. For example, it compares keys via.equals
to 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 class
es 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,
derivedStateOf
doesn’t do any caching; it merely provides a value that subscribes to any [State
object] reads and will update its self when a dependency changes. Soremember
andderivedStateOf
do 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
a
orb
changes, 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
derivedStateOf
can 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
sum
is only read in the local part of the composition. An important property ofderivedStateOf
objects is that they change as part of the same snapshot transaction as their inputs; readers won’t see a state of the world wherea
orb
change butsum
hasn’t been updated yet. And if those inputs do change, only places wheresum
is 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 wherea
orb
changed
so it’s kind of like an observable
by lazy
that 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!