Is there a `fun WindowInsets.asPaddingValues(): Pa...
# compose
s
Is there a
fun WindowInsets.asPaddingValues(): PaddingValues
alternative which does respect incoming already consumed paddings?
Modifier.windowInsetsPadding
does work, but I need my value as
PaddingValues
so that I can pass it as
contentPadding
to a
LazyColumn
Is the answer still that we must still do the onConsumedWindowInsetsChanged dance manually?
I am doing this rn
Copy code
var consumedWindowInsets by remember { mutableStateOf(WindowInsets(0.dp)) }
LazyColumn(
 contentPadding = WindowInsets.safeDrawing
  .exclude(consumedWindowInsets)
  .asPaddingValues(),
 modifier = Modifier.onConsumedWindowInsetsChanged {
  consumedWindowInsets = it
 }
)
But I feel like there is room for an API here which reads the composition local for you and does this for you instead.
a
Yeah, the manual dance is still necessary.
reads the composition local
Inset consumption is done at the
ModifierLocal
level, not via a composition local, so there isn’t a general way to pull out the consumed inset values from
@Composable
unless you’re at a particular spot in the hierarchy, which is what
Modifier.onConsumedWindowInsetsChanged
can do, since its a
Modifier
s
I see, of course. The only way that surfaces is in modifiers that are extending from
ModifierLocalConsumer
but that is as you said, only for modifiers
I think I understood this last year as well, but it took me one year to forget about how all of this is hooked up together. I might be back with the same question in 2025, no promises
😄 1
a
Exactly. Also there’s a slightly better way to use
onConsumedWindowInsetsChanged
in this case:
Copy code
val safeDrawingInsets = WindowInsets.safeDrawing
val insetsForContentPadding = remember { MutableWindowInsets() }

LazyColumn(
 contentPadding = insetsForContentPadding.asPaddingValues(),
 modifier = Modifier.onConsumedWindowInsetsChanged {
  insetsForContentPadding.insets = safeDrawingInsets.exclude(it)
 }
)
That should allow you to have the correct consumed inset values on the first frame
And this should be equivalent too actually, which is closer to your original setup:
Copy code
var consumedWindowInsets = remember { MutableWindowInsets() }
LazyColumn(
 contentPadding = WindowInsets.safeDrawing
  .exclude(consumedWindowInsets)
  .asPaddingValues(),
 modifier = Modifier.onConsumedWindowInsetsChanged {
  consumedWindowInsets.insets = it
 }
)
s
Mr. First frame at it again! So is this because instead of initializing a mutable state, then reading it initially, then setting it from inside the lambda we need to wait for one recomposition to happen before we get the final value? But with this we are now creating the MutableWindowInsets object but we're mutating it internally the moment we hit that lambda? And due to the ordering of things, the internal value of it is read after that lambda was hit?
a
Precisely
And its important that the internals of both
WindowInsets
and
PaddingValues
are all “lazy”, they don’t calculate the values upon construction, they defer the reads until they are needed
👍 1
s
Looks like yet another trick up my compose sleeve. What can I say, thanks so much Alex, once again!
a
You’re welcome!
At the risk of creating nightmares about pointers: State holders and nested state holders creates a giant system of pointers that’s entirely observable and reactive by design
This resolves the first frame issue by creating an additional layer of pointers: Instead of manipulating the directly calculated values for the spacing in composition, we only manipulate pointers (that are internally capable of directly calculating values) in composition
If you print out the result of
asPaddingValues().toString()
, you should see the whole chain of operations used to create the final “pointer”
PaddingValues
that’s used to drive the content padding
🦠 1
s
Btw very good tip about doing the
toString()
on the PaddingValues, it prints out the chain as you said, it gave me a good picture of what I was dealing with here!