BenjO
07/24/2020, 11:41 AMFoobar
composable displaying Foo
and Bar
children composables. Foobar
has two state variables.
@Composable
fun Foobar(fooFlow: StateFlow<Long>, barFlow: StateFlow<Long>) {
val foo by fooFlow.collectAsState()
val bar by barFlow.collectAsState()
Column {
Foo(foo)
Bar(bar)
}
}
@Composable
fun Foo(value: Long) {
Text("Foo $value")
}
@Composable
fun Bar(value: Long) {
Text("Bar $value")
}
Every time either fooFlow
or barFlow
receives a new value, both Foo
and Bar
are recomposed (onCommit is triggered) . I was hoping that only the composable using the flow value would be refreshed.
The only solution I see is to pass each StateFlow down to the children. I’m I correct ?Zach Klippenstein (he/him) [MOD]
07/24/2020, 12:37 PMBenjO
07/24/2020, 12:42 PMI believe the invocation of Foo and Bar will be skipped if the value hasn’t changed since the last composition.This is not what I’ve seen during my tests. (I also tried to use StructurallyEquals to be sure)
this is a perfectly fine way to write this code, and keeping the state flow out of the children is probably the right choiceI know !
why are you concerned?I’m writing a dashboard screen displaying multiple sensors informations (speed, location, orientation). Each sensor is represented by a flow, and I don’t want to refresh the whole screen because one flow emits more frequently than another
Zach Klippenstein (he/him) [MOD]
07/24/2020, 12:45 PMBenjO
07/24/2020, 12:46 PMZach Klippenstein (he/him) [MOD]
07/24/2020, 12:54 PM@Stable
or a related annotation (eg @Immutable
, which assures the compiler that values of the type will never change at all), or (I thought) if it's a primitive type. The documentation on the Stable annotation has more information. For types that don't satisfy this requirement, the compiler plays it safe since it can't be sure that two values which return true for equals
are actually identical.
If you're using data classes, this means the functions calls would only be skipped if your data class is annotated with one of these annotations. If all your properties are Immutable, use @Immutable
for example.BenjO
07/24/2020, 1:02 PM@Immutable
did the trick 👏 (on my personal project with my data classes)This is a stronger promise than `val` as it promises that the value will never change not only that values cannot be changed through a setter.
I think it was exactly what I was looking for.
You’re also right not to try optimizing things too early before the 1.0Zach Klippenstein (he/him) [MOD]
07/24/2020, 1:04 PMBenjO
07/24/2020, 1:05 PMZach Klippenstein (he/him) [MOD]
07/24/2020, 1:08 PMLeland Richardson [G]
07/27/2020, 3:59 PMdata class Foo(val value: Int)
is not inferred as a stable type. Why? Well, the fact that val value: Int
is a property that can’t change is only clear based on the declaration, but not on the public API that is produced. In terms of JVM ABI, it would be perfectly fine to change this class to be class Foo { val value get() = Random.nextInt() }
. So the problem is that val
just signals whether a property is assignable, it does not indicate whether or not its value will ever change. If we infer the latter, we need to make sure that the inferred result is true, even if the jar it was compiled against is replaced with a different one with a different implementation.
3. In order to do (2) properly, we need to change our analysis strategy to something that can be optimized away during full program analysis (such as R8). We have a plan for how to do this, but it is unlikely to be implemented before alpha, but will likely be a requirement for 1.0.
4. In the interim, unfortunately, it means that annotations like @Immutable
and @Stable
can have an outsized impact on the performance of otherwise very reasonable code.BenjO
07/28/2020, 11:51 AMonCommit()
effect of each composable.
I double checked my tests and now I understand why I said that equal primitive parameters caused a recomposition. Here is the trick :
At first I wrote Foobar
, Foo
and Bar
as accepting generic types. Here no matter what type you passed in (primitive or not), Foo and Bar were both recomposed each time any value changes in Foobar.
Then, to ask my question I quickly rewrote the test, removed the generic and replaced it with Long
types. In this case, you were both right, recomposition only occurs for values that change.
Here is a sample Activity illustrating both cases. I don’t know if it is an intended behavior, but I fell into the trap ^^Leland Richardson [G]
07/30/2020, 2:11 PMBenjO
07/30/2020, 3:31 PM