Status Update
Comments
mo...@google.com <mo...@google.com>
va...@gmail.com <va...@gmail.com> #2
+1, can confirm the same issue in Compose 1.5.2 as well.
va...@gmail.com <va...@gmail.com> #3
Here is a simple version of counter having the same issue with method reference not skipping recomposition.
@Composable
fun Counter() {
val viewModel = viewModel<TestViewModel>()
val counter by viewModel.counterFlow.collectAsStateWithLifecycle()
Log.d("Compose", "Counter Recomposed")
Column(modifier = Modifier.fillMaxSize()) {
Text("$counter")
MyButton("Increment Counter", viewModel::increment)
}
}
@Composable
fun MyButton(title: String, onClick: () -> Unit) {
Button(onClick = onClick) {
Text(title)
}
Log.d("Compose", "MyButton Recomposed")
}
class TestViewModel: ViewModel() {
private val _counterFlow = MutableStateFlow(0)
val counterFlow: StateFlow<Int> = _counterFlow
fun increment() {
_counterFlow.value += 1
}
}
ch...@google.com <ch...@google.com> #4
The stability of the lambda is not the issue. The expression viewModel::increment
produces a new instance each time it is called and we use instance equality to compare the lambdas. Because the lambda is a new instance very time viewModel::increment
is evaluated, it prevent all methods this value is passed to from skipping.
The compose compiler plugin will memoize lambdas automatically but not method reference expressions. The can be worked around by using { viewModel.increment() }
instead. This will memoize the lambda based on the captured viewModel
and will use the same instance of the lambda i if the instance of viewModel
is the same.
We want to be able to memoize the method reference3 expression as well as lambda but it currently would require an additional hook into the compiler that he already have. We are working with JetBrains to resolve this to avoid duplication we are currently planning on waiting until the K2 compiler is landed to avoid adding additional features to the existing front-end.
ze...@gmail.com <ze...@gmail.com> #5
va...@gmail.com <va...@gmail.com> #6
Gotcha, tried lambda version { viewModel.increment() }
but that too ends up recomposing MyButton
. viewModel
instance is the same on recomposition
@Composable
fun Counter() {
val viewModel = viewModel<TestViewModel>()
val counter by viewModel.counterFlow.collectAsStateWithLifecycle()
Log.e("Compose", "Counter Recomposed")
Column(modifier = Modifier.fillMaxSize()) {
Text("$counter")
MyButton("Increment Counter", { viewModel.increment() })
}
}
ch...@google.com <ch...@google.com> #7
The TestViewModel
is not inferred or marked @Stable
so it will always generate a new instance of the lambda.
The reason is that the StateFlow
is not stable so we don't infer the type as stable so adding @Stable
to the type is required.
We are experimenting with changing the stability rules as this is admittedly very confusing. With the new rules the @Stable
would not be necessary.
va...@gmail.com <va...@gmail.com> #8
Thanks. Yes, any improvement over here is much appreciated.
ch...@google.com <ch...@google.com> #9
I am marking this bug "as intended" as this is the behavior we intended and we are tracking the feature to add memoizing method references and "strong skipping mode" separately.
ch...@google.com <ch...@google.com> #11
Definitely would love to support this but it requires help from the Kotlin compiler team which is currently heads down on shipping K2.
Description
Jetpack Compose component(s) used:
Android Studio Build:
Kotlin version:
Steps to Reproduce or Code Sample to Reproduce:
See
In short: when updating from Compose 1.1.1 to Compose 1.4.1 (or 1.4.0), when using a method reference instead of a lambda, composables are still recomposed instead of skipped.
To reproduce:
- Clone the repository
- Start the application
- Click a couple of times on the 'ViewModel Lambda Test' button which adds a name to the end of the names above the button
- Notice that all names are recomposed. This is expected because the lambda is not stable.
- Now click a couple of times on the Method Reference Test which add names above that button
- Notice that all names are recomposed as well. I would not expect this since the method reference is stable
- The Remembered Lambda Button behaves as expected where only the name that is added is composes. The other names are skipped.