Bradleycorn
12/05/2020, 3:33 PM@Composable
fun FancyText(text: String) {
val formattedText = remember(text) { computeTextFormatting(text) }
Text(formattedText)
}
Is the use of remember
necessary/valuable here? Seems like the only time this would get recomposed is if text
changes, and in that case we’d want to re-compute the formattedText
value. In other words, we’ll always re-compute the formattedText
value, so do we even need remember
?Dominaezzz
12/05/2020, 3:43 PMBradleycorn
12/05/2020, 3:46 PMremember
any (non-trivial) calculated values?Dominaezzz
12/05/2020, 3:47 PMBradleycorn
12/05/2020, 3:51 PM@Composable
fun AlphabeticalList(items: List<String) {
val aList = items.filter { it.startsWith("A") }
val bList = items.filter { it.startswith("B") }
....
}
figured I should probably use remember(items)
on both of thoseDominaezzz
12/05/2020, 3:59 PMAdam Powell
12/05/2020, 4:01 PMjim
12/05/2020, 4:03 PMAdam Powell
12/05/2020, 4:03 PMDominaezzz
12/05/2020, 4:04 PMjim
12/05/2020, 4:04 PMAdam Powell
12/05/2020, 4:04 PMDominaezzz
12/05/2020, 4:04 PMAdam Powell
12/05/2020, 4:05 PMBradleycorn
12/05/2020, 4:05 PMAdam Powell
12/05/2020, 4:06 PM.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 of `data class`es or large collections where each element will be comparedDominaezzz
12/05/2020, 4:10 PMval formattedText = remember(text) { computeTextFormatting(text) }
be replaced with derivedStateOf { computeTextFormatting(text) }
?jim
12/05/2020, 4:14 PMderivedStateOf
doesn't do any caching; it merely provides a value that subscribes to any model reads and will update its self when a dependency changes. So remember
and derivedStateOf
do orthogonal things.derivedStateOf
is super useful, but only if the things inside the lambda are reading from models.State
objects.Dominaezzz
12/05/2020, 4:16 PMBradleycorn
12/05/2020, 4:17 PMderivedStateOf
yet …jim
12/05/2020, 4:18 PMvar a by mutableStateOf(0)
var b by mutableStateOf(0)
val sum = derivedStateOf { a + b }
If either a
or b
changes, then sum will also automatically change. No memory involved, yes updates/subscriptions involved.Dominaezzz
12/05/2020, 4:19 PMval sum = a + b
?Bradleycorn
12/05/2020, 4:20 PMAdam Powell
12/05/2020, 4:21 PMsum
is only read in the local part of the composition. An important property of derivedStateOf
objects is that they change as part of the same snapshot transaction as their inputs; readers won't see a state of the world where a
or b
change but sum
hasn't been updated yet. And if those inputs do change, only places where sum is read will invalidate, not the place where it was computed.jim
12/05/2020, 4:23 PMderivedStateOf
can be used outside of composition to build all sorts of fun data structures that cache computations before passing them into composables.Dominaezzz
12/05/2020, 4:24 PMAdam Powell
12/05/2020, 4:25 PMclass FancyState {
var a by mutableStateOf(0)
var b by mutableStateOf(0)
val result by derivedStateOf {
somethingVeryExpensiveWith(a, b)
}
}
then the expensive operation will be cached and only run when needed, and conceptually as part of the same snapshot transaction where a or b changedby lazy
that can change over timeDominaezzz
12/05/2020, 4:27 PMAdam Powell
12/05/2020, 4:28 PMsnapshotFlow {}
for other consumption outside composition if you'd likeDominaezzz
12/05/2020, 4:29 PMsnapshotFlow
could be used outside composition!State
and ended up just using MutableStateFlow
.Adam Powell
12/05/2020, 4:31 PMsnapshotFlow
do try to avoid round-trip conversions where you end up doing a .collectAsState
for a flow that started as snapshot state to begin with 🙂jim
12/05/2020, 4:31 PMBradleycorn
12/05/2020, 4:39 PMjim
12/05/2020, 5:48 PMremember
? That seems wrong. As a general rule of thumb, IMO, if you are using remember outside of a parameter's default value expression, you're probably writing smelly code.Bradleycorn
12/05/2020, 5:48 PMval postList by remember(viewModel) {
// A Flow of posts from a room DB.
viewModel.recentPosts
}.collectAsState(initial = listOf())
jim
12/05/2020, 5:49 PMBradleycorn
12/05/2020, 5:50 PMremember
… Let me see if I can find that.Dominaezzz
12/05/2020, 5:51 PMBradleycorn
12/05/2020, 5:51 PMFlow
and use some operators on it, like flowOf(1,2,3).map { it * 2}.collectAsState()
Dominaezzz
12/05/2020, 5:52 PMproduceState
I think.jim
12/05/2020, 5:53 PMBradleycorn
12/05/2020, 5:54 PMproduceState
…
val postList by produceState(initial = listOf(), viewModel) {
viewModel.recentPosts.collect. { value = it }
}
jim
12/05/2020, 5:58 PMproduceState
is marginally better I suppose, still does a remember internally.Bradleycorn
12/05/2020, 5:58 PMproduceState
, remember
, and derivedStateOf
seem like a sane way to go about it …jim
12/05/2020, 5:59 PMmodel.recentPosts
needed to be fast and return a list for consumption by some other API?State<ImmutableList>
or SnapshotStateList
). I would do that, and then there is no need for the remember
in the middle of your composable.Adam Powell
12/05/2020, 6:12 PMBradleycorn
12/05/2020, 6:12 PMcollect
it there (and not, use a terminal operator like say, first()
), right? If the database changes, I need to update state, so …jim
12/05/2020, 6:13 PMBradleycorn
12/05/2020, 6:16 PMViewModel
I’m working with that is scoped to the Activity, it can be difficult to get a proper scope in the ViewModel that would be canceled, should the user navigate elsewhere and the composable removed from the treeproduceState
is what I know, so I tend to use it for everything. 🙂 Hence all these questions to try and broaden my knowledge base and toolset.jim
12/05/2020, 6:25 PMproduceState
is marginally better than remember
.Bradleycorn
12/05/2020, 6:29 PMColton Idle
12/05/2020, 9:33 PMWhy do you have thatI'm so worried about writing smelly code. 😢 I know it's inevitable that I will, but I almost with we could just lint check everything that seems even slightly questionable... lol Lint Check: "Are you sure you want to do this, because you probably don't?"? That seems wrong. As a general rule of thumb, IMO, if you are using remember outside of a parameter's default value expression, you're probably writing smelly code.remember
Adam Powell
12/05/2020, 9:42 PMBradleycorn
12/05/2020, 11:20 PM@Composable
fun MyScreen(...) {
val someStateValue by produceState(...)
val anotherValue by remember { someExpensiveMethod() }
ScreenContent(someStateValue, anotherValue)
}
This seems to work well for me so far. I can easily create a @Preview of the “Content” composable, and I can easily test the “Content” composable as well.bruno.aybar
12/06/2020, 12:10 AM