I’m trying to build a compose layout that depends ...
# compose
l
I’m trying to build a compose layout that depends on orientation. I have the following code, but it never responds properly to the first orientation change (The collect never runs). I rotate my phone from portrait to landscape, and the value of orientation is still ORIENTATION_PORTRAIT. Rotating back to portrait and back to landscape works, though. What am I doing wrong here?
Copy code
var orientation by remember { mutableStateOf(Configuration.ORIENTATION_PORTRAIT) }
val config = LocalConfiguration.current

LaunchedEffect(config) {
    // Save any changes to the orientation value on the configuration object
    snapshotFlow { config.orientation }
        .collect { orientation = it }
}
a
Why not
val orientation = LocalConfiguration.current.orientation
? There shouldn’t be a need to observe it separately, since
LocalConfiguration.current
will update appropriately itself. Also, I’d separately recommend double checking that the portrait vs landscape decision leads to he behavior you want. Is there a width window size class that’s more relevant? With foldables, tablets, and multi-window mode, you can see cases where your window is almost square, or where the switch from landscape to portrait might result in having more screen space to work with
l
There’s a few controls (this is a video player, so speed, slider, step back, play, step forward, and some other tools). The design wanted one row in landscape and two rows in portrait.
Using the orientation directly appears to work.
I initially wrote this in a much earlier build of Compose (but hadn’t added orientation to the list of config changes in the manifest, so it didn’t do anything). It seems like the snapshotFlow should still emit a value, though. Should I file a bug?
a
I am not a designer, but maybe it would make sense to show one row if there’s enough space to show all of the buttons, and two rows otherwise? I would expect the initial code to sort of work, although it would likely take a frame to resolve to the correct value
There definitely is something funky going on here, but I think it’s explainable:
Configuration.equals
returns
true
if the instance of
Configuration
is the same. So the
LaunchedEffect
won’t rerun when the instance stored to
config
itself doesn’t change, because the old
config
object instance is equal to the new
config
instance, even though the value that the configuration has is different. Then,
snapshotFlow
will only output one orientation, because it is capturing that single
config
instance, which doesn’t change.
LocalConfiguration
gets around that by an extremely rare use of a
neverEqualPolicy
, probably for this reason, so updates to
LocalConfiguration.current
will always trigger updates, even if the reference hasn’t changed. So TL;DR:
Configuration
is special, and you want to avoid the cyclic phase dependency anyway
l
If I also want to run an arbitrary method in the collect block, should I just replace this with
LaunchedEffect(orientation)
instead?
a
That should work, once you query the configuration for a basic integer or boolean type, you should be back in predictable behavior territory. Although a
LaunchedEffect
based on the orientation changing itself seems suspicious to me too 😄 . What does that method do?
l
I have a thread that creates ImageBitmaps, and I want to get the threading right between setting the size and drawing. For now, my plan is to pause the process that creates these bitmaps during a configuration change.
a
Are those bitmaps the size of the whole window that might be changing?
l
They’re the same size as the composable that holds them.
I have a layout that lays out the composables and also informs the VM what size the child is. I need to have paused the running process some time before this, though.
I’m also trying to separate this logic from Compose as much as possible, since this is a multiplatform project (produces CGImage on iOS), so I want to lean towards notifying the VM of changes over handling changes in Compose.
a
The composable might change size without an orientation change happening. Your app’s window could go from a large portrait size to a smaller portrait size, for instance
l
Would this be in the case of a folding phone/split screen?
a
Right, any combination of multi-window mode, freeform windowing, foldable devices, etc.
l
I’ll have to check those cases.
a
If the size of the bitmaps you’re producing are for the size of a specific composable, I’d base your logic closer on when the size of that composable changes. That’ll probably end up being more flexible than relying on an activity-level configuration change as a signal
l
I’ll look into changing that. Having to deal with Compose and UIKit (a SwiftUI conversion hasn’t been prioritized, so we’re stuck on UIKit for now) tends to make it harder to integrate the logic that closely.