Sirelon
Sirelon

Reputation: 7046

Orientation on Jetpack Compose

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.

Upvotes: 49

Views: 37959

Answers (9)

Santanu Sur
Santanu Sur

Reputation: 11477

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 orientation by mutableStateOf("Portrait")

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) {
        orientation = "Landscape" // this will automatically change the text to landscape
    } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
        orientation = "Portrait"   // this will automatically change the text to portrait
    }
}

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

Upvotes: 5

AALS
AALS

Reputation: 11

just a simplified clean method for compose:

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

Upvotes: 0

Muthukrishnan Rajendran
Muthukrishnan Rajendran

Reputation: 11622

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")
        }
    }
}

Upvotes: 95

RariteD
RariteD

Reputation: 21

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 }

Upvotes: 2

Nimisha V
Nimisha V

Reputation: 469

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
            },
        )
    }
}

Upvotes: 0

F.Mysir
F.Mysir

Reputation: 4166

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")
        }
    }

Upvotes: 3

Yashovardhan99
Yashovardhan99

Reputation: 938

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

Upvotes: 2

Hans
Hans

Reputation: 1915

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) })

Upvotes: 1

Tom
Tom

Reputation: 7804

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()
    }
}

Upvotes: 19

Related Questions