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
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
- The problem — Android's device diversity — Android runs on phones (360dp wide), tablets (840dp+ wide), foldables (unfolded 600-900dp), Chromebooks, TVs, and cars. A single fixed layout that looks good on a Pixel 7 looks terrible on a Galaxy Z Fold 5 unfolded. Adaptive UI is not optional for production apps in 2026.
- WindowSizeClass — the three buckets — Jetpack WindowManager's WindowSizeClass classifies width and height into Compact (<600dp), Medium (600-840dp), and Expanded (840dp+). Use calculateWindowSizeClass(activity) in Compose. Base layout decisions on size class, not raw dp, to avoid device-specific branching.
- Canonical layouts — list-detail, feed, supporting panel — Google defines three canonical adaptive layouts: List-Detail (email/contacts), Feed (social/news), and Supporting Panel (map+list). These cover 90% of real app screens. Implement them with ListDetailPaneScaffold (Compose) or SlidingPaneLayout (View system).
- ListDetailPaneScaffold — the modern API — Part of Compose adaptive library (androidx.compose.material3.adaptive). Automatically shows list-only on Compact, list+detail side by side on Medium/Expanded. Handles back navigation, pane animations, and two-pane state automatically. Replaces manual two-fragment layouts.
- Foldable support — Jetpack WindowManager — Foldable devices have a hinge. Use WindowInfoTracker.getOrCreate(activity).windowLayoutInfo to observe FoldingFeature — the hinge position and state (FLAT, HALF_OPENED). Avoid placing content under the hinge. Adapt layouts to be 'hinge-aware'.
- FoldingFeature states — FLAT: device fully open (tablet mode). HALF_OPENED: tabletop or book mode. Orientation: HORIZONTAL (tabletop — phone on table hinged horizontally) or VERTICAL (book mode — held like a book). Each requires a different layout adaptation. Google Meet uses tabletop mode to show video on the top half and controls on the bottom.
- Multi-window and split-screen — Since Android 7.0, apps can run in split-screen or freeform windows. Your Activity receives configuration changes with the new window size. Test by enabling multi-window in developer options and dragging your app to half the screen. Never assume the window is the full screen.
- SlidingPaneLayout — View-based adaptive two pane — For apps still using Views, SlidingPaneLayout shows two panes side-by-side when space allows, and stacks them on small screens. NavigationRailView + BottomNavigationView can be swapped based on width. Compose-based apps should prefer ListDetailPaneScaffold.
- Adaptive navigation — Bottom vs Rail vs Drawer — Compact width: BottomNavigationBar (max 5 items). Medium width: NavigationRail (vertical, left side). Expanded width: NavigationDrawer (always visible). Compose NavigationSuiteScaffold (Material 3 adaptive) switches automatically based on WindowSizeClass.
- Large screen testing — emulators and physical devices — Android Studio ships Pixel Tablet, Pixel Fold, and Resizable emulators. Test with the Resizable emulator to drag window size dynamically. Physical device access: Firebase Test Lab includes foldable devices. The Large Screen Gallery at developer.android.com shows canonical implementations.
- Minimum touch target and density independence — On large screens, users may use a mouse or stylus. Touch targets remain 48dp minimum. Use dp everywhere — never hardcode pixel values. Large screens use the same dp grid; a 360dp column will be narrower relative to the screen, so use weight-based layouts (Modifier.weight()) instead of fixed widths.
- Activity embedding — Android 12L+ — Activity embedding splits a single task into two Activities side by side using an XML rule file or the Jetpack WindowManager embedding API. Google Contacts uses this to show the contact list Activity and contact detail Activity simultaneously on tablets without a full rewrite to Compose.
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. currentWindowAdaptiveInfo().windowSizeClass retrieves the current WindowSizeClass — this recomposes automatically when the window size changes (rotation, multi-window, foldable)
- 2. The when block branches on windowWidthSizeClass — COMPACT renders a single-pane list; MEDIUM and EXPANDED render two panes side by side
- 3. ListDetailPaneScaffold manages all two-pane logic: how much space each pane gets, animations when navigating between panes, and back-press behavior
- 4. AnimatedPane wraps each pane with enter/exit animations aligned to the Material 3 motion spec for adaptive transitions
- 5. EmptyDetailPlaceholder shows when no product is selected on Expanded — avoids an empty right pane, which is confusing on tablets
- 6. WindowInfoTracker.getOrCreate(activity).windowLayoutInfo returns a Flow of WindowLayoutInfo — collected with collectAsStateWithLifecycle to respect lifecycle
- 7. filterIsInstance() extracts the hinge feature from the list of display features (which may also include cutouts)
- 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?
Show answer
Explain like I'm 5
Fun fact
Hands-on challenge
More resources
- Adaptive layouts — Android Guide (Android Developers)
- WindowSizeClass — Jetpack (Android Developers)
- Support foldable devices — Jetpack WindowManager (Android Developers)
- ListDetailPaneScaffold (Android Developers)
- Large screen canonical layouts (Android Developers)