State of Compose 2023 results are in! Click here to learn more
Published on

πŸš€ How to create Responsive Layouts in Jetpack Compose

Authors

There is a big chance that your app is going to be displayed on various different screen sizes and form factors.

Today we are going through how to adapt your app's designs to look great on any kind of screen sizes and also how to implement it.

tldr: What is responsive design?

Similar to how water adapts to different containers, your UI needs to adapt to the screen they are displayed on.

This approach to make design visible on all different types of screens is called responsive design.

How to design for multiple screen sizes as a developer

Here are the simple rules I use in my designs when working with responsive layouts. I have used these rules on multiple UI frameworks and technologies (building for mobile, desktop and web) and have worked well in most scenarios:

  • Start designing for bigger screens and then move to the smaller ones. It is much simpler to hide things instead of trying to find a place to add them on the screen later.
  • Do not assume you are running on a phone, laptop, tv given the screen size (unless you are only targeting a specific form factor).
  • Start by swapping horizontal layouts (Row) for vertical ones (Column) when going from the big screen to a smaller one.
  • As a last resort you can modify the sizing of individual elements of the screen. If you do this first, there are going to be too many moving pieces in your designs and you will lose track easily.
  • Never change the size of icons and touch targets. Everything needs to be easy to use on any screen size
  • Visual Content such as images and videos are great elements to adjust size (big beautiful images on big displays)

PS: Use the Modifier.minimumInteractiveComponentSize() in Material 3 so that you do not need to worry about sizing.

PS 2: the minimum target size to fit any human finger is 48dp x 48dp

How to build responsive layouts using BoxWithConstraints

BoxWithConstraints is a composable that provides you with its sizing at a given time.

This makes it a good candidate for a screen level composable as it is aware of its available space.

How to build a list/details panel using BoxWithConstraints

A typical responsive design pattern (usually found in messaging apps) is the list/detail.

On smaller screens you would display a list of conversations. On a bigger screen you can display the full conversation selected, in addition to the list of conversations.

BoxWithConstraints exposes its dimensions using the maxWidth and maxHeight properties:

@Composable
fun ListDetailsExample() {
    BoxWithConstraints(Modifier.fillMaxSize()) {
        val isSmallScreen = maxWidth < 600.dp

        if (isSmallScreen) {
            ConversationsList()
        } else {
            var conversationId by remember {
                mutableStateOf(-1)
            }
            Row(Modifier.fillMaxWidth()) {
                ConversationsList(
                    modifier = Modifier.weight(1f),
                    onConversationSelected = {
                        conversationId = it
                    })
                ConversationDetails(
                    modifier = Modifier.weight(2f),
                    selectedConversation = conversationId
                )
            }
        }
    }
}

How to build responsive layouts using Material 3 WindowSizeClass

The Material 3 WindowSizeClass library gives you information about the Window you are currently running in.

This is similar to using BoxWithConstraints but instead of having the sizing in dp, the library gives you a token describing the viewport you are have available. This comes in three sizes: Compact, Medium, Expanded.

Add the dependency in your project:

// app/build.gradle

dependencies {
    implementation "androidx.compose.material3:material3-window-size-class:1.1.0"
}

and use calculateWindowSizeClass() in your activity. Whenever the screen size changes (during configuration changes) a new size class will be emitted:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            AppTheme {
                val sizeClass = calculateWindowSizeClass(activity = this)

                val showOnePanel = sizeClass.widthSizeClass == WindowWidthSizeClass.Compact

                if (showOnePanel) {
                    ConversationsList()
                } else {
                    var conversationId by remember {
                        mutableStateOf(-1)
                    }
                    Row(Modifier.fillMaxWidth()) {
                        ConversationsList(
                            modifier = Modifier.weight(1f),
                            onConversationSelected = {
                                conversationId = it
                            })
                        ConversationDetails(
                            modifier = Modifier.weight(2f),
                            selectedConversation = conversationId
                        )
                    }
                }
            }
        }
    }
}

as calculateWindowSizeClass() requires an activity you would have to use it either to your app level composable, or pass down from the composable function's parameters.

Common responsive layout patterns

Being aware of the screen size you are running on is the core of building responsive layouts.

Other than that it is all about using the appropriate components and Modifiers for the right job. Here is a list of the most common ones:

  • Use Row/Column and provide their child composables with Modifier.weight() to make them take up the available space.
  • Use FlowRow/FlowColumn to make their child composables wrap to the next line if there is not enough space.
  • Use Column/Row arrangements with SpaceBetween,SpaceEvenly or SpaceAround to distribute the space between their children.
  • Let Images take as much space as possible. Consider giving them a Modifier.aspectRatio() and ContentScale.Crop to make them look good on any screen size.
  • Swap your LazyColumn/LazyRow with LazyGrids and change number of cells depending on the screen size. This way you have a list on mobile and a grid on tablets. You can do this by passing GridCells.Fixed() to the cell parameter of your LazyGrid.

In terms of libraries, the once that stood out to me but I have yet to use them are:

That is all for building responsive layout in Jetpack Compose for today. We went through how to design for multiple screens as a developer, saw the main components behind building responsive layouts, and saw common patterns and libraries to use.

See you all in the next one.