Hey! It is clear to me how `State` parameters are ...
# compose-android
l
Hey! It is clear to me how
State
parameters are correctly evaluated by a
RecomposeScope
for changes. However, it seems we can get the same performance with a stable lambda based provider pattern. I am not sure by what mechanism that works? Can someone shine some light on that for me? Example in thread.
Copy code
@Composable
fun ComponentState(animatingValueState: State<Int>) {
    Row {
        CoolTextState(animatingValueState = animatingValueState)
        Spacer(modifier = Modifier.weight(1f))
        Icon(imageVector = Icons.Rounded.AccountBox)
    }
}

@Composable
fun CoolTextState(animatingValueState: State<Int>) {
    Text(text = animatingValueState.value.toString())
}
Copy code
@Composable
fun ComponentProvider(animatingValueProvider: () -> Int) {
    Row {
        CoolTextProvider(animatingValueProvider = animatingValueProvider)
        Spacer(modifier = Modifier.weight(1f))
        Icon(imageVector = Icons.Rounded.AccountBox)
    }
}

@Composable
fun CoolTextProvider(animatingValueProvider: () -> Int) {
    Text(text = animatingValueProvider().toString())
}
These both correctly and intelligently recompose
s
Yeah both work here, but State<T> is just much less flexible than just () -> T. So you should always go for the lambda approach instead
l
Do you understand the mechanism by which a lambda is evaluated for changes?
State
is obviously a special class that the
RecomposeScope
can listen to for changes, but the lambda return value seems to also be “listened to” so that it can intelligently recompose when that return value changes. I wonder how that behavior plays with the capture’s backing values. What if it is just capturing a
State
backed value, what if it is remembered by backed by an unstable value? It looks like I will need to do some testing in order to really understand. I would love to see some docs or an article that go over this.
s
This should help out with understanding what recomposition scopes are, and how it's important where the state is read to see what will be recomposed. https://dev.to/zachklipp/scoped-recomposition-jetpack-compose-what-happens-when-state-changes-l78 As far as how it knows to recompose by itself, whenever you call .value on a State object, you inform the composition that you are doing a state read in this scope and you register a read observer there. When you do that through a lambda that's no different, you still read from State in composition, so a read observer is still registered, so you still get to be informed about state changes. There's definitely some article that describes this better, but I can't find it atm 👀
l
Ahh yes. I am quite familiar with that first article. I am mostly interested in that lambda capture dynamic. Hopefully you can sniff those articles out 🤞
s
Yeah if I do I'll link back here. But the gist is still just that when you call the lambda, and that lambda does a state read, it's the same as if you passed State down there and did .value on it.
l
So if it were just a raw value, let’s say:
Copy code
var value = "string"
val stringProvider = remember { { value } }

FunComposable(stringProvider)
Would
FunComposable
be intelligently recomposed when
value
changed?
🚫 1
s
No, because value is not backed by MutableState
🙌 1
l
Alright well that makes enough sense to me. That article is interesting and was useful for understanding recomposition as a whole. It is interesting though, that the provider pattern really only works if it is backed by some type of
State
.
That actually makes a more sense to me though. I think I have what I need. I was imagining some intelligent closure evaluation method that was causing the provider pattern to achieve intelligent recomposition, but really I was just getting lucky, as the state I was returning from my lambdas was backed by a
State
. The fact that the initial invocation of the lambda returns a
State
is what is giving the desired behavior, it is not that the lambda is repeatedly re-evaluated.
s
Yup, indeed. The same functionality is what makes the same work when you have an interface which may just expose something as a Int for example, but if the implementation contains that int in a State object, boom, you get the observation for free again. Look inside LazyListState I think for an example where this is done with the scroll state etc. If my memory serves me well.
l
My man. Thank you for all the thoughts and assistance. You are an unsung hero.
🙌 1