π How to create Responsive Layouts in Jetpack Compose
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 Modifier
s for the right job. Here is a list of the most common ones:
- Use
Row
/Column
and provide their child composables withModifier.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 withSpaceBetween
,SpaceEvenly
orSpaceAround
to distribute the space between their children. - Let
Image
s take as much space as possible. Consider giving them aModifier.aspectRatio()
andContentScale.Crop
to make them look good on any screen size. - Swap your
LazyColumn
/LazyRow
withLazyGrids
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 passingGridCells.Fixed()
to the cell parameter of yourLazyGrid
.
In terms of libraries, the once that stood out to me but I have yet to use them are:
- Thomas KΓΌnneth's Adaptive Scaffold for building for foldables.
- Accompanist TwoPane layout for building list/detail layouts.
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.