https://kotlinlang.org logo
#compose-desktop
Title
# compose-desktop
g

Garret Yoder

03/07/2024, 8:46 PM
Is there a good way to prevent the huge pile of recompositions that happen when you resize a window, or is that just expected behavior since everything now has to relayout?
a

Alex Vanyo

03/07/2024, 8:50 PM
That’s expected behavior - if the window size changes, and the UI has to render differently or layout different things, recompositions is a part of how that happens. What is the reason for looking to prevent that from happening?
👍🏻 1
g

Garret Yoder

03/07/2024, 8:55 PM
I'm just going through some general optimizations. Not all composables recompose when the parent window size changes though.
I think it's a side effect of me using BoxWithConstraints that causes this one view to do it for every frame during a resize, which makes sense since it's altering the order of composition to get the size early.
p

Pablichjenkov

03/07/2024, 9:01 PM
Are you using a BoxWithConstraint as a root Composable? Or very close to the root, I have noticed a decrease in performance when doing that.
g

Garret Yoder

03/07/2024, 9:01 PM
I am yeah, I'm very much open to alternatives. I'm using it to get the window size (multiplatform)
p

Pablichjenkov

03/07/2024, 9:02 PM
The alternative I found was to use a custom layout for that. I can place the link here in a second
g

Garret Yoder

03/07/2024, 9:02 PM
I think someone ported the official WindowSizeClass library to multiplatform - but it didn't support letting you define custom bounds and I didn't feel like forking it on the morning I wrote this
All of this was for the root adaptive layout that transitions layout based on screen configuration
I figured it wasn't the most efficient thing and I'd optimize later, hence my question. If you can use the bog standard layout and get the window size ahead of layout, thats great and would work
Check how above Composable uses layout for this matter. I got the code from a stackoverflow question that I can't find now.
It will take one more composition cycle to measure the child Composable but the performance improvement over BoxWithConstraint is noticeable
g

Garret Yoder

03/07/2024, 9:08 PM
Huh that looks pretty simple. I'll try that, thanks!
👍 1
a

Alex Vanyo

03/07/2024, 9:10 PM
That missed composition cycle seems like a scary trade-off to me - you’ll have a missed blank frame, and each time you do that in a nested component, the total number of frames required to resolve to the final state increases
https://github.com/chrisbanes/material3-windowsizeclass-multiplatform is probably the multiplatform window size class implementation you were mentioning, the code for calculating the window size specifically for desktop is here: https://github.com/chrisbanes/material3-windowsizeclass-multiplatform/blob/b562742[…]dx/compose/material3/windowsizeclass/WindowSizeClass.desktop.kt I would see if modifying that a bit to return the raw size would work better
👍 1
p

Pablichjenkov

03/07/2024, 9:12 PM
That is right, what about if having only 1 component like this in the whole hierarchy? Still scary?
Definitely above library is the best approach.
g

Garret Yoder

03/07/2024, 9:14 PM
Yup thats the library I was talking about, I was going to take a look at what it's doing to grab the size as well. The BoxWithConstraints was definitely an early morning "make it work now and optimize later" thing.
p

Pablichjenkov

03/07/2024, 9:14 PM
However, in some cases I would say I don't want the screen/window size but just the available constraints size my parent Composable is passing down.
a

Alex Vanyo

03/07/2024, 9:16 PM
That is right, what about if having only 1 component like this in the whole hierarchy? Still scary?
What you might see as you resize the window is alternating blank frames and frames with content, or the content will lag behind the window resizing - neither of which is great.
🆗 1
g

Garret Yoder

03/07/2024, 9:18 PM
I think I did briefly see the same with BoxWithConstraints where content was a frame or two behind the resize if I resized the window fast enough - I assume it's the same effect just without the black frame.
a

Alex Vanyo

03/07/2024, 9:20 PM
However, in some cases I would say I don’t want the screen/window size but just the available constraints size my parent Composable is passing down.
Right, in those cases the window size wouldn’t work, and you might need to use
BoxWithConstraints
if the content you want in composition changes due to the available size. There’s a couple of ways to avoid
BoxWithConstraints
though - if you keep the same set of things composed, but just laid out differently, you might be able to use a custom layout, and if you are just hiding a piece of content if there isn’t enough space, you can skip placing a piece of content in your custom layout.
👍 1
g

Garret Yoder

03/07/2024, 9:22 PM
That's a good idea too. I think I might just fork and adjust the boundaries for the different window sizes in the library mentioned above since that's going to be the correct way to do it for multiplatform hopefully eventually
ListDetailPaneScaffold
comes to multiplatform since that will solve my use case for needing to care what the window size is anyway.
👍 1
All of this was just me recreating something very similar to that
a

Alex Vanyo

03/07/2024, 9:24 PM
Also https://issuetracker.google.com/issues/305993708 might be interesting to star, it sounds like this isn’t available easily in compose-desktop either.
hopefully eventually
ListDetailPaneScaffold
comes to multiplatform since that will solve my use case for needing to care what the window size is anyway.
That makes me very happy to hear 😄
g

Garret Yoder

03/07/2024, 9:27 PM
Starred that issue, that'd be a great feature! And yeah the. List detail pane looks really cool and like it'd solve the entire AdaptiveLayout composable I just wrote. That hitting multiplatform and then there being an analogue to the older M2 toolbar behavior of enterAlwaysCollapsed are my biggest wishlists for M3 compose. I wrote my own custom decorator for a list to accomplish it, but its not really compatible with Scaffolds.
a

Alexander Maryanovsky

03/07/2024, 10:50 PM
Sorry if I’m barging in with an irrelevant suggestion, but does
LocalWindowInfo.current.containerSize
help?
👀 1
a

Alex Vanyo

03/07/2024, 11:02 PM
Interesting, is that implemented for every platform other than Android?
a

Alexander Maryanovsky

03/07/2024, 11:03 PM
Yes. We needed it for positioning dialogs/popups, and we decided to make it (experimental) public.
We’d be happy if it was upstreamed.
Especially since it seems that K2 will break it: https://youtrack.jetbrains.com/issue/KT-22841
👀 1
g

Garret Yoder

03/08/2024, 2:45 AM
> Sorry if I’m barging in with an irrelevant suggestion, but does
LocalWindowInfo.current.containerSize
help? Don't be sorry at all, all input is appreciated! This looks great and it does work, is there a way to make a derivedstate out of this - or is it just a calculated value and theres no state behind it? It's a composable function so it obviously throws an error. Using it directly would still trigger a recomposition every time the value rolls over, but if theres a way to derivedstate that then I can bucket it in the state and the UI will only do a full recompose any time one of those buckets changes
a

Alexander Maryanovsky

03/08/2024, 12:57 PM
The usual way
Copy code
val windowInfo = LocalWindowInfo.current
val windowSizeBucket by remember(windowInfo) {
    derivedStateOf {
        bucketOf(windowInfo.containerSize)
    }
}
CompositionLocalProvider(LocalWindowSizeBucket provides windowSizeBucket) {
    content()
}
p

Pablichjenkov

03/08/2024, 2:09 PM
You can make a deriveState out of anything. You just need to encapsulate it in a compose State. And then derive from that state. First time I see this
bucketOf
seems that it just does the State encapsulation I mentioned.
g

Garret Yoder

03/08/2024, 3:41 PM
Huh, I learn something new every day in this chat, that's cool. I thought you just couldn't do that because it was a composable state outside of a compose function. is bucketOf an internal method? I couldn't find a reference to it, or it's implementation in the github repo for compose MP. I'd love to read the implementation for that. I'm going through the docs right now and reading because CompositionLocal isn't a feature I've yet used.
a

Alexander Maryanovsky

03/08/2024, 3:45 PM
bucketOf
is a method you would write to map a raw size to a bucket
I was just showing how to do a derived state from the raw window size.
👍 1
g

Garret Yoder

03/08/2024, 3:47 PM
Ah okay got it, thanks! I'll go learn CompositionLocal then, thanks guys.
Copy code
val LocalWindowSizeBucket = compositionLocalOf { AdaptiveLayout.WINDOW_SIZE.MEDIUM }
val windowInfo = LocalWindowInfo.current
val density = LocalDensity.current
val windowSizeBucket by remember(windowInfo) {
    derivedStateOf {
        AdaptiveLayout.WINDOW_SIZE.getSize(windowInfo.containerSize.width pxToDpWith density)
    }
}
CompositionLocalProvider(LocalWindowSizeBucket provides windowSizeBucket) {

}
That works perfectly, and I learned a useful trick. Thanks again guys.
p

Pablichjenkov

03/08/2024, 4:06 PM
Ahh I see.
a

Alexander Maryanovsky

03/08/2024, 4:07 PM
LocalWindowBucketSize should be a global declaration, and density should be one of the keys to remember
👍 1
g

Garret Yoder

03/08/2024, 4:19 PM
You mean lift LocalWindowSizeBucket out of the compose function? It's already a top level val. Also can you clarify what you mean with density, isn't that already from a CompositionLocal?
Oh I see doing
Copy code
val density = LocalDensity.current
val densityInfo = remember { density }
causes the density calc to only be run once so now I'm down to only one recompose when the layout changes, cool!
a

Alexander Maryanovsky

03/08/2024, 4:38 PM
val density = LocalDensity.current
val densityInfo = remember { density }
No. Not sure what you’re trying to do here. I meant that it should be
Copy code
val windowSizeBucket by remember(windowInfo, density) { ... }
but actually that’s not critical. If density changes the derived state calculation will notice it anyway.
val density = LocalDensity.current
val densityInfo = remember { density }
This will not work well if density changes, because you’re caching it unconditionally.
g

Garret Yoder

03/08/2024, 4:40 PM
Oh okay I see, got it that makes sense
I think how the keys in remember are intended to work just clicked in my head, thanks I actually learned a lot in this thread haha.
a

Alexander Maryanovsky

03/08/2024, 5:03 PM
Hmm, on 3rd look I’m not sure anymore. The lambda passed to
derivedStateOf
captures the value of
density
when it’s created. When the density changes the lambda won’t be recreated, so it will continue using the old value.
1
Better test it 🙂
g

Garret Yoder

03/08/2024, 5:45 PM
Hmm. Not sure what I did but now it seems like inside of the compositionlocal it's not actually updating the value, though it is triggering a recompose when the state outside of the compositionlocal changes
Copy code
val LocalWindowSizeBucket = compositionLocalOf { AdaptiveLayout.WINDOW_SIZE.MEDIUM }
val density = LocalDensity.current
val windowInfo = LocalWindowInfo.current
val windowSizeBucket by remember(windowInfo, density) { derivedStateOf { AdaptiveLayout.WINDOW_SIZE.getSize(windowInfo.containerSize.width pxToDpWith density) } }
println(windowSizeBucket)
CompositionLocalProvider(LocalWindowSizeBucket provides windowSizeBucket) {
windowSizeBucket outside of the compositionlocal is properly changing Edit: I found why, the compositionLocalOf needs to be wrapped in a remember.
a

Alexander Maryanovsky

03/08/2024, 8:14 PM
It needs to be a global/toplevel val
Outside the function
Otherwise I’m not sure what the point here is.
If you just want the bucket locally, there’s no need for the composition local.
Copy code
val LocalWindowSizeBucket = compositionLocalOf { AdaptiveLayout.WINDOW_SIZE.MEDIUM }
This needs to be a global val
g

Garret Yoder

03/08/2024, 8:33 PM
Yeah I was still learning CompositionLocal this morning when I did that, I think I've got a good grasp now. I restructured it and made it a global already. Thanks for all the help! I learned a couple neat tricks in this thread and my adaptive layout works pretty well now.
👍 1
51 Views