Lesson 52 of 83 intermediate

Adaptive Android UI: Phones, Tablets, Foldables & Multi-Window

Design once, adapt everywhere — WindowSizeClass, canonical layouts, and foldable support

Open interactive version (quiz + challenge)

Real-world analogy

A responsive UI is like water — it fills whatever container it is poured into. A phone is a small glass, a tablet is a pitcher, and a foldable is a glass that transforms into a pitcher mid-pour. Your UI must flow smoothly through all of them without spilling.

What is it?

Adaptive Android UI is the practice of designing layouts that respond to the available window size and device form factor — phone, tablet, foldable, multi-window. Android provides WindowSizeClass, ListDetailPaneScaffold, Jetpack WindowManager (for foldables), and NavigationSuiteScaffold to implement these patterns declaratively.

Real-world relevance

Gmail on a Pixel Fold shows the inbox list and email detail side by side when unfolded, and stacks them when folded. Google Keep uses tabletop mode to show note content on the top half and a drawing toolbar on the bottom. Microsoft Outlook and Spotify both use NavigationRail on medium-width screens, switching from BottomNavigation seen on phones.

Key points

Code example

// ── WindowSizeClass-based adaptive layout ────────────────────
@Composable
fun AdaptiveShopScreen(
    products: List<Product>,
    selectedProduct: Product?,
    onProductSelected: (Product) -> Unit
) {
    val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass

    when (windowSizeClass.windowWidthSizeClass) {
        WindowWidthSizeClass.COMPACT -> {
            // Phone: list only, navigate to detail on click
            ProductListScreen(products, onProductSelected)
        }
        WindowWidthSizeClass.MEDIUM,
        WindowWidthSizeClass.EXPANDED -> {
            // Tablet/foldable: two panes side by side
            ListDetailPaneScaffold(
                listPane = {
                    AnimatedPane {
                        ProductListScreen(products, onProductSelected)
                    }
                },
                detailPane = {
                    AnimatedPane {
                        selectedProduct?.let { ProductDetailScreen(it) }
                            ?: EmptyDetailPlaceholder()
                    }
                }
            )
        }
    }
}

// ── Foldable hinge awareness ──────────────────────────────────
@Composable
fun HingeAwareLayout(activity: ComponentActivity) {
    val windowInfoTracker = WindowInfoTracker.getOrCreate(activity)

    val layoutInfo by windowInfoTracker
        .windowLayoutInfo(activity)
        .collectAsStateWithLifecycle(null)

    val foldingFeature = layoutInfo?.displayFeatures
        ?.filterIsInstance<FoldingFeature>()
        ?.firstOrNull()

    if (foldingFeature?.state == FoldingFeature.State.HALF_OPENED &&
        foldingFeature.orientation == FoldingFeature.Orientation.HORIZONTAL) {
        // Tabletop mode: split content above and below hinge
        TabletopLayout()
    } else {
        // Default layout
        DefaultLayout()
    }
}

Line-by-line walkthrough

  1. 1. currentWindowAdaptiveInfo().windowSizeClass retrieves the current WindowSizeClass — this recomposes automatically when the window size changes (rotation, multi-window, foldable)
  2. 2. The when block branches on windowWidthSizeClass — COMPACT renders a single-pane list; MEDIUM and EXPANDED render two panes side by side
  3. 3. ListDetailPaneScaffold manages all two-pane logic: how much space each pane gets, animations when navigating between panes, and back-press behavior
  4. 4. AnimatedPane wraps each pane with enter/exit animations aligned to the Material 3 motion spec for adaptive transitions
  5. 5. EmptyDetailPlaceholder shows when no product is selected on Expanded — avoids an empty right pane, which is confusing on tablets
  6. 6. WindowInfoTracker.getOrCreate(activity).windowLayoutInfo returns a Flow of WindowLayoutInfo — collected with collectAsStateWithLifecycle to respect lifecycle
  7. 7. filterIsInstance() extracts the hinge feature from the list of display features (which may also include cutouts)
  8. 8. The condition FoldingFeature.State.HALF_OPENED with HORIZONTAL orientation identifies tabletop mode — the device is sitting flat with the screen bent open like a laptop

Spot the bug

@Composable
fun ArticleScreen(articles: List<Article>) {
    val screenWidth = LocalConfiguration.current.screenWidthDp

    if (screenWidth > 600) {
        Row {
            ArticleListPane(articles, Modifier.width(300.dp))
            ArticleDetailPane(Modifier.fillMaxWidth())
        }
    } else {
        ArticleListPane(articles, Modifier.fillMaxWidth())
    }
}
Need a hint?
This uses raw dp values instead of WindowSizeClass, and the two-pane layout has a sizing bug. What two issues should a senior engineer fix?
Show answer
Two bugs: (1) LocalConfiguration.current.screenWidthDp returns the full screen width — not the window width. In multi-window mode, the app may occupy 400dp of a 900dp screen. screenWidthDp would return 900dp, triggering the two-pane layout incorrectly in a narrow split-screen window. Fix: use currentWindowAdaptiveInfo().windowSizeClass.windowWidthSizeClass and branch on COMPACT vs MEDIUM/EXPANDED. (2) ArticleListPane is fixed at Modifier.width(300.dp) while ArticleDetailPane uses fillMaxWidth(). This means the detail pane ignores the 300dp already consumed by the list. On a 700dp window, the detail pane would overflow by 300dp. Fix: use Modifier.weight() for both panes — e.g., list Modifier.weight(1f) and detail Modifier.weight(2f) — so they share available space proportionally regardless of screen width.

Explain like I'm 5

Think of your app like a shop display. On a phone, you have a tiny shelf — you can only show a few items at a time, and you need to walk to the back room for details. On a tablet or foldable, you have a big counter — you can show the items list on the left and the item details on the right at the same time. The items don't change; the shelf adapts.

Fun fact

The Samsung Galaxy Z Fold series has a hinge positioned at approximately 50% of the unfolded screen width. Jetpack WindowManager's FoldingFeature.bounds gives the exact pixel rectangle of the hinge, allowing apps to position content precisely above and below — or left and right — of the physical crease.

Hands-on challenge

Build a Compose screen that displays a list of notes. On Compact width, tapping a note navigates to a full-screen detail view. On Medium/Expanded width, list and detail appear side by side using ListDetailPaneScaffold. Add hinge detection: if a FoldingFeature with HORIZONTAL orientation and HALF_OPENED state is detected, display note metadata above the hinge and the note body below.

More resources

Open interactive version (quiz + challenge) ← Back to course: Android Interview Mastery