Designing for Foldable Devices and Large Screens
The era of a single phone screen size is over. With Samsung’s Galaxy Z Fold and Z Flip series gaining traction, Oppo’s Find N, and Google’s continued push for large screen support in Android 12L, mobile developers need to think beyond the traditional phone form factor.
Foldable devices and tablets are no longer edge cases. Samsung shipped millions of foldable devices in 2021, and the category is growing rapidly. If your app does not handle screen size changes gracefully, a growing segment of users will have a poor experience.
This guide covers the technical and design strategies for building apps that work beautifully across foldable devices, tablets, and traditional phones.
Understanding the Foldable Landscape
Device Categories in 2022
Book-style foldables (Samsung Galaxy Z Fold3, Oppo Find N): These unfold from a phone-sized outer screen to a tablet-sized inner screen. Your app must handle both sizes and the transition between them.
Flip-style foldables (Samsung Galaxy Z Flip3): These fold to a compact size and open to a standard phone screen. The primary design consideration is the small cover screen.
Tablets and large screens (Samsung Galaxy Tab S8, Lenovo Tab P12 Pro): Traditional tablets that benefit from the same responsive layout techniques as foldables.
Chrome OS devices: Chromebooks can run Android apps, often in a window. Your app should handle arbitrary window sizes.
Key Dimensions
- Galaxy Z Fold3 cover screen: 832 x 2268 pixels (24.5:9 aspect ratio)
- Galaxy Z Fold3 inner screen: 1768 x 2208 pixels (roughly square)
- Typical tablet: 1600 x 2560 pixels or similar
- Chromebook window: Varies widely
Responsive Lay
out Strategies
Window Size Classes
Google has introduced window size classes as a way to categorise screen sizes. Rather than targeting specific devices, design for three width classes:
- Compact: Under 600dp (standard phones)
- Medium: 600dp to 839dp (small tablets, foldables in unfolded state)
- Expanded: 840dp and above (large tablets, desktop)
// Using Jetpack WindowManager to determine size class
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val metrics = WindowMetricsCalculator
.getOrCreate()
.computeCurrentWindowMetrics(this)
val widthDp = metrics.bounds.width() /
resources.displayMetrics.density
val layoutType = when {
widthDp < 600f -> LayoutType.COMPACT
widthDp < 840f -> LayoutType.MEDIUM
else -> LayoutType.EXPANDED
}
setContentView(layoutType)
}
}
Adaptive Layouts with Jetpack Compose
Jetpack Compose makes adaptive layouts straightforward with BoxWithConstraints:
@Composable
fun AdaptiveLayout() {
BoxWithConstraints {
when {
maxWidth < 600.dp -> {
// Phone layout: single column
PhoneLayout()
}
maxWidth < 840.dp -> {
// Medium: list-detail side by side
MediumLayout()
}
else -> {
// Large: full three-pane layout
ExpandedLayout()
}
}
}
}
@Composable
fun MediumLayout() {
Row(modifier = Modifier.fillMaxSize()) {
// List pane
ItemList(
modifier = Modifier
.weight(0.4f)
.fillMaxHeight()
)
// Detail pane
ItemDetail(
modifier = Modifier
.weight(0.6f)
.fillMaxHeight()
)
}
}
XML-Based Adaptive Layouts
If you are using the traditional View system, use resource qualifiers:
{/* res/layout/activity_main.xml (compact) */}
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment android:name=".ItemListFragment" />
</FrameLayout>
{/* res/layout-w600dp/activity_main.xml (medium) */}
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<fragment
android:name=".ItemListFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<fragment
android:name=".ItemDetailFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2" />
</LinearLayout>
Handlin
g Fold State Changes
When a user folds or unfolds a device, your app receives a configuration change. The Jetpack WindowManager library provides APIs to handle this:
class FoldAwareActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch(Dispatchers.Main) {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
WindowInfoTracker.getOrCreate(this@FoldAwareActivity)
.windowLayoutInfo(this@FoldAwareActivity)
.collect { layoutInfo ->
handleLayoutInfo(layoutInfo)
}
}
}
}
private fun handleLayoutInfo(layoutInfo: WindowLayoutInfo) {
val foldingFeature = layoutInfo.displayFeatures
.filterIsInstance<FoldingFeature>()
.firstOrNull()
if (foldingFeature != null) {
when (foldingFeature.state) {
FoldingFeature.State.HALF_OPENED -> {
// Device is half-opened (like a laptop)
enableTableTopMode(foldingFeature)
}
FoldingFeature.State.FLAT -> {
// Device is fully open
enableFullScreenMode()
}
}
} else {
// No fold, standard layout
enableStandardMode()
}
}
}
Table-Top Mode
When a foldable device is half-opened and placed on a surface (like a laptop), you can split your UI at the fold:
private fun enableTableTopMode(fold: FoldingFeature) {
if (fold.orientation == FoldingFeature.Orientation.HORIZONTAL) {
// Fold is horizontal: top half for content, bottom half for controls
val foldPosition = fold.bounds.top
contentView.layoutParams = contentView.layoutParams.apply {
height = foldPosition
}
controlsView.layoutParams = controlsView.layoutParams.apply {
height = screenHeight - foldPosition
}
}
}
This is particularly useful for video players (video on top, controls on bottom), camera apps (viewfinder on top, shutter and gallery on bottom), and video calling apps.
Multi-Window Support
Android’s multi-window mode lets your app run alongside other apps. This is common on tablets and foldables:
Declaring Support
{/* AndroidManifest.xml */}
<activity
android:name=".MainActivity"
android:resizeableActivity="true"
android:configChanges="screenSize|screenLayout|orientation|smallestScreenSize">
{/* Support picture-in-picture */}
<meta-data
android:name="android.support.PICTURE_IN_PICTURE"
android:value="true" />
</activity>
Handling Multi-Window Lifecycle
override fun onMultiWindowModeChanged(
isInMultiWindowMode: Boolean,
newConfig: Configuration
) {
super.onMultiWindowModeChanged(isInMultiWindowMode, newConfig)
if (isInMultiWindowMode) {
// Simplify UI for smaller space
hideSecondaryContent()
} else {
// Restore full UI
showSecondaryContent()
}
}
Design Patterns for Large Screens
List-Detail Pattern
The most common pattern for large screens. Show a list and detail view side by side instead of navigating between them:
@Composable
fun ListDetailLayout(
items: List<Item>,
selectedItem: Item?,
onItemSelected: (Item) -> Unit
) {
Row(modifier = Modifier.fillMaxSize()) {
LazyColumn(
modifier = Modifier
.weight(0.35f)
.fillMaxHeight()
) {
items(items) { item ->
ItemRow(
item = item,
isSelected = item == selectedItem,
onClick = { onItemSelected(item) }
)
}
}
Divider(
modifier = Modifier
.fillMaxHeight()
.width(1.dp)
)
Box(
modifier = Modifier
.weight(0.65f)
.fillMaxHeight()
) {
if (selectedItem != null) {
ItemDetailView(item = selectedItem)
} else {
EmptyStateView(message = "Select an item")
}
}
}
}
Navigation Rail
On larger screens, replace bottom navigation with a navigation rail on the left side:
@Composable
fun AdaptiveNavigation(
windowWidth: Dp,
content: @Composable () -> Unit
) {
if (windowWidth < 600.dp) {
Scaffold(
bottomBar = { BottomNavigationBar() }
) { content() }
} else {
Row {
NavigationRail(
modifier = Modifier.fillMaxHeight()
) {
// Navigation items
}
content()
}
}
}
Avoiding Stretched Layouts
A common mistake is allowing content to stretch across the full width of a large screen. Constrain content width for readability:
@Composable
fun ConstrainedContent(content: @Composable () -> Unit) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.TopCenter
) {
Box(
modifier = Modifier
.widthIn(max = 840.dp)
.fillMaxHeight()
) {
content()
}
}
}
Testing Across Form Factors
Emulator Configurations
Create emulator profiles for key form factors:
- Phone: Pixel 5 (1080 x 2340, 6.0 inches)
- Foldable unfolded: 7.6-inch Foldable (1768 x 2208)
- Foldable cover: Use the Fold outer display profile
- Tablet: Pixel C or Nexus 9
Configuration Change Testing
Test configuration changes aggressively:
- Fold/unfold transitions
- Rotation changes
- Multi-window enter/exit
- Drag-resize in multi-window mode
Ensure your app preserves state through all these transitions. If you are using ViewModel properly, state should survive configuration changes automatically.
Practical Recommendations
-
Start with responsive layouts now, even if your primary audience uses phones. The cost of implementing responsive layouts from the start is far lower than retrofitting them later.
-
Use ConstraintLayout or Compose for complex layouts. These handle varying dimensions naturally.
-
Test on real foldable hardware if possible. The emulator is good but does not capture the physical experience of folding and unfolding.
-
Do not letterbox. Users dislike seeing black bars around your app on their expensive foldable device. Make your app fill the available space.
-
Consider drag and drop. On large screens and foldables, users expect to drag content between apps. Implementing drag and drop support makes your app feel native to the form factor.
Conclusion
Foldable devices and large screens are not a passing trend. Samsung’s investment in the category, Google’s Android 12L release focused on large screens, and the growing market share of tablets all point to a future where adaptive layouts are expected rather than optional.
The good news is that the tools are ready. Jetpack WindowManager, Compose’s built-in responsive capabilities, and Google’s window size class system provide everything you need to build apps that work beautifully across every screen size.
For help building adaptive mobile experiences for the Australian market, contact eawesome. We build apps that work seamlessly from compact phones to the largest foldable displays.