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")
            }
        }
    }
}

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:

  1. Phone: Pixel 5 (1080 x 2340, 6.0 inches)
  2. Foldable unfolded: 7.6-inch Foldable (1768 x 2208)
  3. Foldable cover: Use the Fold outer display profile
  4. 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

  1. 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.

  2. Use ConstraintLayout or Compose for complex layouts. These handle varying dimensions naturally.

  3. Test on real foldable hardware if possible. The emulator is good but does not capture the physical experience of folding and unfolding.

  4. Do not letterbox. Users dislike seeing black bars around your app on their expensive foldable device. Make your app fill the available space.

  5. 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.