https://kotlinlang.org logo
#compose
Title
# compose
m

Mbt925

10/23/2023, 7:18 AM
Hi, I am facing an unexpected behavior regarding the recomposition count with the following composable:
Copy code
@Composable
 fun Test() {
     var isShown by remember { mutableStateOf(false) }
     val opacity by animateFloatAsState(targetValue = if (isShown) 0f else 0.5f, label = "")
 
     Column(Modifier.statusBarsPadding()) {
         Button(
             onClick = { isShown = !isShown }) { Text(text = "Toggle text") }
         
         Text(text = "Hello world", modifier = Modifier.graphicsLayer { alpha = opacity })
     }
 }
The above composable works fine. The recomposition of Hello world text is skipped as expected. However, if I replace
graphicsLayer { alpha = opacity }
with
Modifier.animateAlpha { opacity }
using the following function, it recomposes every time the button is clicked:
Copy code
@Stable
 fun Modifier.animateAlpha(alpha: () -> Float) = this.graphicsLayer {
     this.alpha = alpha()
 }
Any insight into the issue is appreciated.
a

Albert Chang

10/23/2023, 8:48 AM
The above composable works fine. It doesn't recompose Hello world text every time, but only once.
I doubt if that's true. I would expect the function to be recomposed every time the button is clicked because without a recomposition, the
targetValue
of
animateFloatAsState
can't be updated.
m

Mbt925

10/23/2023, 9:04 AM
@Albert Chang you're right. I meant the composition and layout phases are not re-executed. Edited the question.
a

Albert Chang

10/23/2023, 9:09 AM
That doesn't change anything. As I said,
without a recomposition, the
targetValue
of
animateFloatAsState
can't be updated.
And by recomposition I mean all the phases including composition phase.
m

Mbt925

10/23/2023, 9:11 AM
The hello world composable doesn't need recomposition, but only the Test composable
a

Albert Chang

10/23/2023, 9:20 AM
The hello world text will be recomposed in both cases. Read the
Caveat: Inline composable functions
section in this post for the reason.
m

Mbt925

10/23/2023, 9:28 AM
The article is talking about a different topic. Please run the code and check the recomposition count.
a

Albert Chang

10/23/2023, 9:49 AM
I see what you mean. So instead of "the text is not recomposed" it's better to say "the recomposition of the text is skipped" so that others can more easily understand.
👍 1
This is because the memoization of lambdas in compose. In the first case, the lambda
{ alpha = opacity }
is memoized (i.e. it will always be the same instance across compositions) and so the modifier will be considered the same, in which case the
Text
call can be skipped. But in the second case, while
{ opacity }
is memoized, the lambda in the
Modifier.animateAlpha
function isn't, as memoization only happens in composable functions. Therefore the lambda passed to
Modifier.graphicsLayer
won't be the same instance, and so the modifier won't be considered the same.
👌 1
m

Mbt925

10/23/2023, 10:38 AM
Thanks for the insight. How can I achieve the same result then?
a

Albert Chang

10/23/2023, 10:43 AM
Just use the first form, or use something like this:
Copy code
val lambda = { opacity }
val modifier = remember(lambda) {
    Modifier.animateAlpha(lambda)
}
Btw, avoid premature optimization.
m

Mbt925

10/23/2023, 11:00 AM
This is just a simplified version of the use case. Why is there a
Modifier.alpha
extension, if it doesn't do what it's supposed to do (only affecting the drawing phase)? How are we supposed to delegate complex logic to extension functions on Modifiers then?
a

Albert Chang

10/23/2023, 11:18 AM
Create your own
Modifier.Node
. That's always the best approach.
f

Francesc

10/23/2023, 3:36 PM
Why is there a
Modifier.alpha
extension, if it doesn't do what it's supposed to do
Modifier.alpha
is simpler to use than
graphicsLayer { alpha = x }
if you have a static alpha, not an animating one
👍 1