DEV Community

Cover image for Tiny things on big screens
Thomas Künneth
Thomas Künneth

Posted on

Tiny things on big screens

Tablets and foldables offer plenty of screen real estate, especially when compared to mid-sized smartphones. A lot of proven user interface design patterns help you arrange the content of your app on large devices, for example Master Detail. But what if your app doesn't need all that space? This article explores how to deal with too much room.

To get an idea what I am talking about, take a look at the following screenshot.

A small tool using one screen

Time Calculator can add and subtract hours, minutes, and seconds. Nothing more. So, naturally, there is not much that can take advantage of a big screen. In one screen mode, the user interface looks fine. Here's how the app looks on two screens.

Time Calculator on two screens

The hinge of the device obstructs parts of some user interface elements. But that's not the main issue here. What I am referring to is: the app tries to respect the screen size, but this results in ridiculously large buttons.

This begs the question: Should we do this?

With such a huge virtual dial pad, entering the times to add or subtract is an unpleasant experience, because the fingers need to travel (relatively) large distances. Smaller keys would reduce the movement, presumably resulting in a more natural, fluent input.

Let's try to draw some inspiration from well known Android apps. The following screenshot was taken from a simulated 10 inch device running Android 12L. It shows the Clock app in Timer mode.

The Clock app

The virtual keys are somewhat big, too. But the pad as a whole appears almost centred. Let's see if this works for Time Calculator.

Time Calculator centred on a large screen

Not bad.

You can easily centre your existing user interface with BoxWithConstraints():

BoxWithConstraints(
  modifier = Modifier
    .fillMaxSize()
    .background(color = MaterialTheme.colorScheme.tertiaryContainer),
  contentAlignment = Alignment.Center
) {
  Column(
    modifier = Modifier
      .width(min(maxWidth, 600.dp))
      .background(color = MaterialTheme.colorScheme.surface)
  ) {
  
Enter fullscreen mode Exit fullscreen mode

This way you can set a maximum width, in my example 600 density independent pixels.

So, what's our verdict? Should we go for this? While the coloured boxes to the left and to the right are not particularly pleasing, the user interface looks less inflated. And directing the attention to the centre of the screen is not a bad thing either, right?

The modified version of Time Calculator on a device with a hinge

Well, on a device with a hinge that area may be obstructed. As you can see in the screenshot, the hinge divides the main area of the app. Not pleasing at all.

Let's refine our recipe.

  • On ordinary devices, do nothing
  • On large screens without a hinge that obstructs content, consider centring your user interface horizontally
  • On devices with an obstructing or blocking hinge, show your user interface on one side of the hinge

With Jetpack WindowManager this is quite easy. First, add an implementation dependency to your module-level build.gradle file:

implementation "androidx.window:window:1.0.0-rc01"
Enter fullscreen mode Exit fullscreen mode

When the Activity is created, we need to check window bounds and some hinge-related information. This is done like this:

override fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)
  lifecycleScope.launchWhenResumed {
    setContent {
      val li by WindowInfoTracker.getOrCreate(this@TimeCalculatorActivity)
        .windowLayoutInfo(this@TimeCalculatorActivity).collectAsState(
          initial = null
        )
      val wm = WindowMetricsCalculator.getOrCreate()
        .computeCurrentWindowMetrics(this@TimeCalculatorActivity)
  
  Content(li, wm)
  
Enter fullscreen mode Exit fullscreen mode

Here's what Content() does:

@Composable
fun Content(layoutInfo: WindowLayoutInfo?,
            windowMetrics: WindowMetrics) {
  
  val hingeDef = createHingeDef(layoutInfo, windowMetrics)
  val largeScreen = windowWidthDp(windowMetrics) >= 600.dp
  BoxWithConstraints(
    modifier = Modifier
      .fillMaxSize()
      .background(color =  
         MaterialTheme.colorScheme.tertiaryContainer),
    contentAlignment = if (hingeDef.hasGap and largeScreen)
      Alignment.TopStart
    else
      Alignment.Center
  ) {
    val width = min(
      maxWidth, if (hingeDef.hasGap)
        hingeDef.sizeLeft
      else
        600.dp
    )
    Column(
      modifier = Modifier
        .width(width)
        .background(color = MaterialTheme.colorScheme.surface)
    ) {
      Column(
        
Enter fullscreen mode Exit fullscreen mode

As you can see, my bullet point list can be nicely implemented with a few ifs. createHingeDef() and windowWidthDp() are small helper functions. You can find them here. The following screenshot shows the result:

Hinge-aware version of Time Calculator

I think this looks much better. But it is not ideal yet. Because showing nothing is not very inviting, right?

Adding content

Here's how the Phone app on the Surface Duo Emulator looks like on two screens:

The Phone app on the Surface Duo Emulator in two screen mode

While it currently does a poor job in respecting the hinge, it shows us what we can do with the additional space: display helpful content. This may be:

  • actions (something the user can do)
  • tips or tricks
  • a brief set of instructions

If your app has a settings page, you might want to show it there. But please respect established screen flows and interaction patterns. Also, refrain from diverting the user, for example by showing unrelated information and content. Under no circumstances should you show or present something your user would not expect.

How do we implement this? Fortunately, my HingeDef class can help here, too, because it also contains the size of the gap and the size of the area to its right. Let's look at the implementation in Time Calculator.

BoxWithConstraints(
  modifier = Modifier
    .fillMaxSize()
    .background(color = MaterialTheme.colorScheme.tertiaryContainer),
  contentAlignment = if (hingeDef.hasGap and largeScreen)
    Alignment.TopStart
  else
    Alignment.Center
) {
  val width = min(
    maxWidth, if (hingeDef.hasGap)
      hingeDef.sizeLeft
    else
      600.dp
  )
  Row(
    modifier = Modifier
      .fillMaxSize()
      .background(color = MaterialTheme.colorScheme.surface)
  ) {
    Column(
      modifier = Modifier
        .width(width)
    ) {
      Column() {
        
      }
      Spacer(modifier = Modifier.height(16.dp))
      Column() {
        
      }
    }
    Spacer(modifier = Modifier.width(hingeDef.widthGap))
    Column(
      modifier = Modifier
        .width(hingeDef.sizeRight)
        .fillMaxHeight()
        .padding(16.dp),
      horizontalAlignment = Alignment.CenterHorizontally
    ) {
      Text(text = stringResource(id = R.string.info1))
      Spacer(modifier = Modifier.height(16.dp))
      Text(text = stringResource(id = R.string.info2))
      Spacer(modifier = Modifier.height(16.dp))
      Text(text = stringResource(id = R.string.info3))
      Spacer(modifier = Modifier.height(16.dp))
      Text(text = stringResource(id = R.string.info4))
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

So, all we do is adding three composables to a Row().

Time Caluclator shows useful information on large devices and foldables

Conclusion

Small utilities may not have enough content to really fill large screens. You can enhance the experience by adding worthwhile additional information. However, you should never show content unrelated to the core idea of your app. And you must refrain from inventing new interaction patterns just to populate the display. Have you written small tools and faced such issues? Please share your thoughts in the comments.

Discussion (0)