I would like to use unconsumed WindowInsets as con...
# compose-android
t
I would like to use unconsumed WindowInsets as contentPadding in LazyColumn. Currently i use following code:
Copy code
LazyColumn(
    contentPadding = WindowInsets.systemBars.asPaddingValues(),
) {
But this does not consider already consumed insets. Is there a method to get unconsumed insets? I looked into the inset modifier api and they use internal variables to keep track of consumed insets.
s
I had more or less the exact same need as you, and the only way to “extract” the consumed window insets from inside the composition local that they use to communicate it, is through the use of
onConsumedWindowInsetsChanged
modifier. Here’s what I do https://github.com/HedvigInsurance/android/blob/e72564f334bcd3a61d1f62a9eeccc5d63d[…]tlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt
t
Thank you very much for pointing this out. Unfortunately very complicated solution for such a common use case. But thank you Stylianos.
I currently do have another workaround. I just put one item at the top and one at the end of the list with a Spacher that have a modifier. And for the horizontal padding i put it into the normal modifier and not using content padding at all.
s
And for the horizontal padding i put it into the normal modifier and not using content padding at all
What do you mean by “normal” modifier here?
t
Copy code
LazyColumn(
    modifier = Modifier
        .windowInsetsPadding(WindowInsets.systemBars.only(WindowInsetsSides.Horizontal)
        )
) {
    item {
        Spacer(Modifier
            .windowInsetsPadding(WindowInsets.systemBars.only(<http://WindowInsetsSides.Top|WindowInsetsSides.Top>))
        )
    }
    items ...
    item {
        Spacer(Modifier
            .windowInsetsPadding(WindowInsets.systemBars.only(WindowInsetsSides.Bottom))
        )
    }
}
This is my solution but also a little bit too complicated for my taste 😞
Hmm maybe i will try to create a simpler solution using your approach.
s
No this works too. But with a caveat! If your items ever need to go outside of those horizontal bounds, they will appear cut-off. Funny enough that I was showing this off just the other day to some colleagues of mine Basically you get the cut-off as these two images show
As opposed to this, which you can only achieve by using
contentPadding
and not applying the horizontal insets to the entire content
Basically this 😅 But this presentation didn’t show this little trick you gotta do to also handle consumed insets while also using
contentPadding
, which is discussed here.
t
I came up with following code to make it a little bit easier to use on many places:
Copy code
class PaddingValueProvider(
    val modifier: Modifier
) {
    var paddingValues: PaddingValues by mutableStateOf(PaddingValues())
        internal set
}

@Composable
fun insetsPaddingValuesProvider(insets: WindowInsets): PaddingValueProvider {
    val density = LocalDensity.current
    var consumedWindowInsets by remember { mutableStateOf(WindowInsets(0.dp)) }
    val provider = remember {
        PaddingValueProvider(
            modifier = Modifier.onConsumedWindowInsetsChanged {
                log("consumedInsets: $it")
                consumedWindowInsets = it
            }
        )
    }

    LaunchedEffect(Unit) {
        snapshotFlow { consumedWindowInsets }.collect {
            provider.paddingValues = insets.exclude(consumedWindowInsets).asPaddingValues(density)
            log("Padding values: ${provider.paddingValues}")
        }
    }

    return provider
}
Usage looks than like this:
Copy code
val paddingValueProvider = insetsPaddingValuesProvider(insets = WindowInsets.systemBars)

LazyColumn(
    modifier = paddingValueProvider.modifier,
    contentPadding = paddingValueProvider.paddingValues
) {
    items ...
}
What do you think?
Or maybe could it be possible to get directly the paddingvalues without adding a modifier. Maybe by using an invisible Spacer?
s
Hmm not sure about this modifier which applies to nothing and is just stored as a variable, does that even work?
t
Ok it looks like this code will also work and is much easier:
Copy code
@Composable
fun insetsPaddingValues(insets: WindowInsets): PaddingValues {
    val density = LocalDensity.current
    var paddingValues by remember { mutableStateOf(PaddingValues()) }
    Spacer(modifier = Modifier.onConsumedWindowInsetsChanged(
        block = {
            paddingValues = insets.exclude(it).asPaddingValues(density)
        }
    ))
    return paddingValues
}
Usage is than very easy:
Copy code
LazyColumn(
    contentPadding = insetsPaddingValues(insets = WindowInsets.systemBars)
) {
    items ...
}
In my tests it works but would love to hear some thoughts about this solution.
s
So you got a composable function, which returns an instance of
PaddingValues
but also at the same time emits some UI by using the
Spacer
. I think that’s a big no-no, as mentioned here https://android.googlesource.com/platform/frameworks/support/+/androidx-main/compose/docs/compose-api-guidelines.md#emit-xor-return-a-value
t
Yes you are right. But it is a spacer which will take 0,0 size 😄 I think i will use it until the Android devs add some usable API
But of course you could also use the provider approach which is a little bit easier to make mistakes when you forget to add the modifier anywhere.
s
If you’re feeling like it, it would be nice to make a feature request about this, and provide what you did to solve your issue by having to break the compose api guidelines to make it happen. This new API that you mention won’t ever happen if we don’t ask for it 😊
👍 1
t
I will file a feature request later and post the thread here.
thank you color 1
s
Thank you!