45

Is there any Orientation helpers class in Jetpack Compose, like Flutter has https://flutter.dev/docs/cookbook/design/orientation ?? I need to know when orientation have been changed to adjust my layout properly.

9 Answers 9

84

We can use LocalConfiguration to determine what the current orientation is, but the following code does not help to listen for configuration changes:

@Composable
fun ConfigChangeExample() {
    val configuration = LocalConfiguration.current
    when (configuration.orientation) {
        Configuration.ORIENTATION_LANDSCAPE -> {
            Text("Landscape")
        }
        else -> {
            Text("Portrait")
        }
    }
}
4
  • Excellent! Thank you
    – Sirelon
    Commented Nov 9, 2020 at 19:42
  • 8
    Won't all compasables get recomposed as the activity gets destroyed and created after configuration change? Commented Nov 2, 2021 at 11:22
  • 1
    To "listen" to orientation change it's enough to check the orientation in onCreate method, and pass this value to your composable. It's becaus onCreate() is called on each orientation change due to activity recreation.
    – Waldmann
    Commented Feb 23, 2022 at 3:24
  • 2
    @DanielWeidensdörfer ... Activity itself will be destroyed and recreated on orientation change, So Composables will also be destroyed and recreated but view models instances are retained
    – Devrath
    Commented Oct 29, 2023 at 14:18
18

To observe the orientation, we can create a snapshot flow to observe changes to the orientation which feeds into a state variable you can use directly.

var orientation by remember { mutableStateOf(Configuration.ORIENTATION_PORTRAIT) }

val configuration = LocalConfiguration.current

// If our configuration changes then this will launch a new coroutine scope for it
LaunchedEffect(configuration) {
    // Save any changes to the orientation value on the configuration object
    snapshotFlow { configuration.orientation }
        .collect { orientation = it }
}

when (orientation) {
    Configuration.ORIENTATION_LANDSCAPE -> {
        LandscapeContent()
    }
    else -> {
        PortraitContent()
    }
}
1
  • I'm not actually sure if worthwhile tbh - still learning myself!
    – Tom
    Commented May 20, 2021 at 19:04
5

We can use state in jectpack compose, so that a composable re-composes itself when the state changes.

An example of listening to the configuration change using state is follows:-

@Composable
fun ShowConfig(config: String) {
   Text(text = "$config!")
}

Keep a config state in activity:-

var state by mutableStateOf("Potrait")

Then listen to the config changes in the activity and on configuration just update the state by the value like:-

override fun onConfigurationChanged(newConfig: Configuration) {
    super.onConfigurationChanged(newConfig)
    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
        state = "Landscape" // this will automatically change the text to landscape
    } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
        state = "Potrait"   // this will automatically change the text to potrait
    }
}

The Greeting composable observes the state of the config string whenever the state of the config string changes it re-composes.

3

You can get it from BoxWithConstraints and compare the width with the height.

Simple example:

    @Composable
    fun ShowScreenOrientation() {
        BoxWithConstraints {
            val mode = remember { mutableStateOf("") }
            mode.value = if (maxWidth < maxHeight) "Portrait" else "Landscape"
            Text(text = "Now it is in ${mode.value} mode")
        }
    }
2

Adding an updated answer based on the new androidx.compose.material3.windowsizeclass library.

Instead of detecting orientation directly, you can now use WindowSizeClass to query the width and height classes. This library is specifically meant for building adaptive layouts and will automatically recompose the layout when your activity's size/orientation changes.

This API gives you the rough size of your width and height (COMPACT, MEDIUM, EXPANDING). This makes it easy to handle devices like foldables and large screen displays. This also takes into account phones using split screen (orientation remains same) and other options which cannot be handled by a simple orientation check.

Here is a simple example I made:-

class MainActivity {
    /* ...... */
    setContent {
        val windowSizeClass = calculateWindowSizeClass(this)
        /* .... */
        MyApp(windowWidthSizeClass = windowSizeClass.widthSizeClass, /* .... */ )
    }
}

@Composable
fun MyApp(windowWidthSizeClass: WindowWidthSizeClass, /* ... */) {
    when(windowWidthSizeClass) {
        WindowWidthSizeClass.Expanded -> // orientation is landscape in most devices including foldables (width 840dp+)
        WindowWidthSizeClass.Medium -> // Most tablets are in landscape, larger unfolded inner displays in portrait (width 600dp+)
        WindowWidthSizeClass.Compact -> // Most phones in portrait
    }
}

Here, I can set the layout to a landscape view when windowWidthSizeClass is equal to WindowWidthSizeClass.Expanded. I can also use the WindowWidthSizeClass.Medium width to optimize my layout for larger devices like tablets and foldables. Lastly, WindowWidthSizeClass.Compact tells me that most mobiles are in portrait and I can update my UI accordingly.

These same enums are also available for the height of the activity but the documentation states -

Most apps can build a responsive UI by considering only the width window size class.

(So far, true for me)

Note that this library is still in alpha and explicitly marked as Experimental so it might change. I just wanted to add this here for anyone trying out Material 3 with Jetpack Compose.

Official Guide - https://developer.android.com/guide/topics/large-screens/support-different-screen-sizes#window_size_classes

Example implementation can be found in the JetNews sample app - https://github.com/android/compose-samples/tree/main/JetNews.

Docs - https://developer.android.com/reference/kotlin/androidx/compose/material3/windowsizeclass/package-summary

Release notes (released in 1.0.0-alpha10, current version - alpha13) - https://developer.android.com/jetpack/androidx/releases/compose-material3#1.0.0-alpha10

1

Here's a little helper for swapping a compose row into a column when turning to portrait-mode. You may want to tweak it a bit for your purposes.

@Composable
fun OrientationSwapper(
    content1: @Composable (modifier: Modifier) -> Unit,
    content2: @Composable (modifier: Modifier) -> Unit
) {
    val portrait = LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT
    if (portrait) {
        Column(verticalArrangement = Arrangement.SpaceEvenly, modifier = Modifier.fillMaxSize()) {
            content1(modifier = Modifier.weight(1f))
            content2(modifier = Modifier.weight(1f))
        }
    } else {
        Row(horizontalArrangement = Arrangement.SpaceEvenly, modifier = Modifier.fillMaxSize()) {
            content1(modifier = Modifier.weight(1f))
            content2(modifier = Modifier.weight(1f))
        }
    }
}

and use as:

    OrientationSwapper(
        content1 = { modifier ->
            Text("foo", modifier = modifier) },
        content2 = {  modifier ->
            Text("bar", modifier = modifier) })
1

You can change values dynamically with this method:

@Composable
fun <T> dynamicOrientationValue(portraitValue: T, landscapeValue: T): T =
    when (LocalConfiguration.current.orientation) {
        Configuration.ORIENTATION_LANDSCAPE -> landscapeValue
        else -> portraitValue
    }

Usage:

SpinBtn(
    modifier = Modifier
        .fillMaxWidth(dynamicOrientationValue(0.6f, 0.3f))
        .align(dynamicOrientationValue(BottomCenter, CenterEnd))
        .padding(32.dp), isSpinning = isSpinning
) { isSpinning = true }
0
var orientation by remember { mutableStateOf(Configuration.ORIENTATION_PORTRAIT) }
LaunchedEffect(configuration) {
    snapshotFlow { configuration.orientation }
        .collect { orientation = it }
}

when (orientation) {
    Configuration.ORIENTATION_LANDSCAPE -> {
       LandScapeScreen(
            isPortrait = false,
        ) {
            activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
            orientation = Configuration.ORIENTATION_PORTRAIT
        }
    }
    else -> {
        PortraitUI(
            isPortrait = true,
            onClick = {
                activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
                orientation = Configuration.ORIENTATION_LANDSCAPE
            },
        )
    }
}
0

just a simplified clean method for compose:

@Composable
fun isLandscape(): Boolean = when (LocalConfiguration.current.orientation){
    Configuration.ORIENTATION_LANDSCAPE -> true
    else -> false
}

Not the answer you're looking for? Browse other questions tagged or ask your own question.