Re-gaining orientation #3
Tags: android, jetpackcompose, kotlin, ux
Published: 2021-03-26 16:37:53

Welcome to the third part of my small series about screen orientation in Jetpack Compose. So far we have an app that in portrait mode features a traditional master-detail display where either master (for example a list of modules) or detail (the contents of one module) cover the whole screen. In landscape mode the screen is divided into two parts.

![Two-column-view in landscape mode](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b05918y8vobr65qtupyf.png)

The width of the list is smaller than the module. This makes sense because

- the details should get as much screen real estate as possible
- the list itself has not that much to show, because its texts are usually just parts of a sentence

At least on smartphones and traditional tablets.

Now let's see how the app looks like on an opened foldable device.

![Running on an opened foldable device](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i44wuhhjp77xjdju4y0t.png)

Well, that's not what we want, right? 

Right.

Please remember, currently the decision whether to show portrait or landscape mode looks like this:

```kotlin
isTwoColumnMode = (LocalConfiguration.current.orientation
    == Configuration.ORIENTATION_LANDSCAPE)
if (isTwoColumnMode)
  Landscape(module)
else
  Portrait(module)
```

Although the foldable device is open, it still is held in portrait mode. 

One way to make the decision smarter is to look at the available screen width. So we might argue that if there are at least 600 density-independent pixels this is enough to show two columns. That's how alternative layout resources have been working for quite a long time.

```kotlin
isTwoColumnMode = LocalConfiguration.current.screenWidthDp >= 600
```

![Emulator showing a completely open foldable](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/auykeq8yeb2ik2fp4hob.png)

But what if the foldable has two screens with a noticeable gap between them?

![The Surface Duo Emulator](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/izrfjkdhes8jqvob2r78.png)

Depending on the content an app wants to present it may make sense to treat the screen real estate as two equally sized blocks. Please recall how my example organizes its composables.

```kotlin
@Composable
fun Landscape(module: MutableState<Module?>) {
  Row(modifier = Modifier.fillMaxSize()) {
    ModuleSelection(
      module = module,
      modifier = Modifier.weight(weight = 0.3f)
    )
    module.value?.let {
      Module(
        module = it,
        modifier = Modifier.weight(0.7f)
      )
    }
  }
}
```

As you can see, `Modifier.weight()` is passed `0.3f` for the module selection and `0.7f` for the module. Instead of hardcoding the values we can easily pass them as parameters.

```kotlin
@Composable
fun Landscape(
  module: MutableState<Module?>,
  weightModuleSelection: Float,
  weightModule: Float
) {
  Row(modifier = Modifier.fillMaxSize()) {
    ModuleSelection(
      module = module,
      modifier = Modifier.weight(weight = weightModuleSelection)
    )
    module.value?.let {
      Module(
        module = it,
        modifier = Modifier.weight(weightModule)
      )
    }
  }
}
```

But how do we determine the ratio? Or, to put it another way, when do we pass `0.5F` twice?

The Jetpack Window Manager component [aims to](https://developer.android.com/jetpack/androidx/releases/window)

> help application developers support new device form factors
> and provide a common API surface for different 
> Window Manager features on both old and new platform
> versions.

To use it we need to add the following line to our dependencies section in the *build.gradle* file of the app module:

```groovy
implementation "androidx.window:window:1.0.0-alpha05"
```

`androidx.window.WindowManager` is the main interaction point with the library.

```kotlin
private var weightModuleSelection by mutableStateOf(0.3F)
private var weightModule by mutableStateOf(0.7F)

class MainActivity : ComponentActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val wm = WindowManager(this)
    wm.registerLayoutChangeCallback(mainExecutor, {
      it.displayFeatures.forEach { feature ->
        (feature as FoldingFeature).run {
          if (isSeparating) {
            weightModuleSelection = 0.5F
            weightModule = 0.5F
          }
        }
      }
    })
    setContent {
      ScreenSizeDemo()
    }
  }
}
```

After obtaining an instance of `WindowManager` we register a callback for layout changes of the window of the current visual `Context`. The callback receives `WindowLayoutInfo` objects, which in turn contain a list of `DisplayFeature`s. According to the doc, a...

> display feature is a distinctive physical attribute located
> within the display panel of the device. It can intrude into
> the application window space and create a visual
> distortion, visual or touch discontinuity, make some area
> invisible or create a logical divider or separation in the
> screen space.

`FoldingFeature` implements this interface. It 

> describes a fold in the flexible display or a hinge 
> between two physical display panels.

`isSeparating` returns `true` if...

> a `FoldingFeature` should be thought of as splitting the
> window into multiple physical areas that can be seen by
> users as logically separate. Display panels connected by
> a hinge are always separated. Folds on flexible screens
> should be treated as separating when they are not
> `STATE_FLAT`. 

On the Surface Duo Emulator the example looks as follows:

![Updated code on the Surface Duo Emulator](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/opxzm1jmixu9t9eod29q.png)

On the stock Android Emulator simulating a foldable the app looks like this:

![Updated code on the stock Android Emulator simulating a foldable](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fvquz5qnxhxi5u7vceev.png)

While both results look quite pretty we have a few thing to do. Can you guess what? Please stay tuned for a follow-up.

---

[Source](https://github.com/tkuenneth/android-demos/tree/master/compose/ScreenSizeDemo)