In lesson 27 you met window size classes and built a first list-detail layout. This lesson goes further. Your app will not only run on a phone held upright. It will run on tablets, on foldables that open and close, on Chromebooks, in split-screen and freeform windows, on a desktop-sized display, and in a window the user drags to any shape. Android's guidance is simple: do not design for devices, design for window sizes.
This lesson covers Implementing Android apps for all screen sizes and Build adaptive apps from Pathway 5 of the Jetpack Compose for Android developers programme (https://developer.android.com/courses/pathways/jetpack-compose-for-android-developers-5), and the adaptive-layouts material linked from it (for example the codelab at https://codelabs.developers.google.com/jetpack-compose-adaptability).
By the end you will be able to:
- Explain why window size, not device type or orientation, drives layout.
- Read window size classes and build breakpoint-based layouts.
- Apply the canonical layouts: list-detail, supporting pane and feed.
- Use the Material 3 adaptive scaffolds and navigation suite (with care about their release status).
- React to foldable postures such as tabletop and book with Jetpack WindowManager.
- Support resizing, configuration changes, keyboard, mouse and stylus input.
- Test adaptive layouts on a resizable emulator.
Big idea: a window is a rectangle whose size can change at any time. Write layouts as functions of that rectangle and every device, present and future, is covered.
Honesty note: the adaptive libraries are updated often and some APIs are marked experimental. Class names below match the libraries at the time of writing; check the official release notes for the current names and stability before shipping.
- Window size classes,
NavigationSuiteScaffoldand the hand-built list-detail (lesson 27). - ViewModel and state hoisting (lessons 25 and 37).
- Navigation Compose (lessons 26 and 55).
- Lazy lists and grids (lessons 21 and 39).
- Android Studio emulators (lesson 4).
Large screens are mainstream
Tablets, foldables and ChromeOS devices are a large audience, and on a big screen a stretched phone layout looks lazy: lines of text 1,000 pixels wide, one small button in a sea of white. Google Play also reviews apps against large-screen quality guidelines, and apps that adapt can get better visibility on those devices. (Exact Play surfacing rules change; check the current documentation.)
Orientation is not the signal
An old habit was "landscape means wide". But a phone in split-screen is narrow even in landscape, and a tablet in portrait is still wide. A window can be resized by the user at any time. So the question to ask is "how many dp wide and tall is my window now?", never "what device is this?".
Window size classes
| Class | Width breakpoint | Typical window |
|---|---|---|
| Compact | below 600 dp | Phone in portrait, small split-screen pane |
| Medium | 600 dp up to 840 dp | Tablet in portrait, unfolded foldable, large phone in landscape |
| Expanded | 840 dp and above | Tablet in landscape, desktop windows |
Newer releases of the window-size-class library add larger breakpoints (such as large and extra-large widths for desktop-like windows). Check the current WindowManager documentation for the full set. Height classes (compact, medium, expanded) exist too; width is usually the one that drives navigation and pane layouts.
Canonical layouts
Rather than invent a layout from scratch, use proven patterns:
| Layout | Idea | Example |
|---|---|---|
| List-detail | A list on one side, the selected item's detail on the other | Email, messages, a student register |
| Supporting pane | A main pane plus a smaller related pane | A document with comments, a lesson with notes |
| Feed | Content cards in a flexible grid that grows columns with width | News, social, product catalogue |
Each layout shows one pane on compact windows (navigating between them) and two panes on wide windows.
Adaptive navigation
| Window | Navigation component |
|---|---|
| Compact | Navigation bar at the bottom |
| Medium | Navigation rail at the side |
| Expanded | Navigation rail or permanent navigation drawer |
NavigationSuiteScaffold chooses automatically (lesson 27).
The adaptive libraries are separate from Compose Material 3. In libs.versions.toml use placeholders for versions and look up the latest stable ones:
[versions]
adaptive = "<latest stable>"
material3 = "<latest stable>"
windowManager = "<latest stable>"
[libraries]
androidx-compose-material3-window-size = { group = "androidx.compose.material3", name = "material3-window-size-class", version.ref = "material3" }
androidx-compose-material3-adaptive = { group = "androidx.compose.material3.adaptive", name = "adaptive", version.ref = "adaptive" }
androidx-compose-material3-adaptive-layout = { group = "androidx.compose.material3.adaptive", name = "adaptive-layout", version.ref = "adaptive" }
androidx-compose-material3-adaptive-navigation = { group = "androidx.compose.material3.adaptive", name = "adaptive-navigation", version.ref = "adaptive" }
androidx-compose-material3-adaptive-navigation-suite = { group = "androidx.compose.material3", name = "material3-adaptive-navigation-suite", version.ref = "material3" }
androidx-window = { group = "androidx.window", name = "window", version.ref = "windowManager" }
Add the entries you need to build.gradle.kts with implementation(libs....). (Coordinates can change as libraries graduate; confirm them in the documentation.)
Calculate the current window size class
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
class MainActivity : ComponentActivity() {
@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
AppTheme {
val sizeClass = calculateWindowSizeClass(this)
SchoolApp(widthClass = sizeClass.widthSizeClass)
}
}
}
}
An alternative that does not need the activity is currentWindowAdaptiveInfo() from the adaptive library, which returns the window size class and fold information as a state-backed value:
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
import androidx.window.core.layout.WindowWidthSizeClass
@Composable
fun SchoolApp() {
val info = currentWindowAdaptiveInfo()
val widthClass = info.windowSizeClass.windowWidthSizeClass
val twoPane = widthClass != WindowWidthSizeClass.COMPACT
// ...
}
(The exact property and type names have shifted between releases, for example towards width-in-dp comparisons such as isWidthAtLeastBreakpoint(...). Check the current API; the idea is stable.)
Rule: pass size information down as parameters (a boolean such as
twoPane, or the size class). Composables deep in the tree should not callcalculateWindowSizeClassthemselves; hoist it once at the top.
Our running example is ClassRegister: a list of students and a detail pane. The state lives in a ViewModel and is shared by both layouts.
data class Student(val id: Int, val name: String, val form: String, val notes: String)
data class RegisterUiState(
val students: List<Student> = emptyList(),
val selectedId: Int? = null
) {
val selected: Student? get() = students.firstOrNull { it.id == selectedId }
}
class RegisterViewModel : ViewModel() {
private val _uiState = MutableStateFlow(
RegisterUiState(
students = listOf(
Student(1, "Adaeze Okoye", "SS2", "Strong in Mathematics."),
Student(2, "Tunde Bello", "SS1", "Needs support in English."),
Student(3, "Funke Adebayo", "SS3", "Prefect. Science class.")
)
)
)
val uiState: StateFlow<RegisterUiState> = _uiState.asStateFlow()
fun select(id: Int?) = _uiState.update { it.copy(selectedId = id) }
}
A simple adaptive screen without the scaffold
@Composable
fun RegisterScreen(
state: RegisterUiState,
twoPane: Boolean,
onSelect: (Int?) -> Unit,
modifier: Modifier = Modifier
) {
if (twoPane) {
Row(modifier.fillMaxSize()) {
StudentList(
students = state.students,
selectedId = state.selectedId,
onSelect = { onSelect(it) },
modifier = Modifier.weight(0.4f)
)
VerticalDivider()
StudentDetail(
student = state.selected,
modifier = Modifier.weight(0.6f)
)
}
} else {
// One pane at a time; back returns to the list
if (state.selected == null) {
StudentList(state.students, null, { onSelect(it) }, modifier.fillMaxSize())
} else {
BackHandler { onSelect(null) }
StudentDetail(state.selected, modifier.fillMaxSize(), onBack = { onSelect(null) })
}
}
}
Observe what is shared: the ViewModel and selectedId. When a user unfolds a phone, twoPane flips to true, and the same selection now appears as the right-hand pane without losing position. That continuity is the heart of an adaptive app.
Why
selectedIdnotselected? An id survives process changes and recomposition cleanly, and the object is looked up from the current list. Storing the whole object can leave stale copies.
The Material 3 adaptive library provides scaffolds that implement canonical layouts, including animation, dividers and window-aware behaviour:
| Scaffold | Layout |
|---|---|
ListDetailPaneScaffold |
List-detail |
SupportingPaneScaffold |
Main pane plus a supporting pane |
NavigableListDetailPaneScaffold, NavigableSupportingPaneScaffold |
The above, plus a navigator that manages which pane is shown and back handling |
At the time of writing, parts of this library are marked @ExperimentalMaterial3AdaptiveApi; opt in explicitly and expect small renames.
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
import androidx.compose.material3.adaptive.layout.AnimatedPane
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole
import androidx.compose.material3.adaptive.navigation.NavigableListDetailPaneScaffold
import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun RegisterAdaptive(students: List<Student>) {
val navigator = rememberListDetailPaneScaffoldNavigator<Int>() // content key: the student id
val scope = rememberCoroutineScope()
NavigableListDetailPaneScaffold(
navigator = navigator,
listPane = {
AnimatedPane {
StudentList(
students = students,
selectedId = navigator.currentDestination?.contentKey,
onSelect = { id ->
scope.launch {
navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, id)
}
}
)
}
},
detailPane = {
AnimatedPane {
val id = navigator.currentDestination?.contentKey
StudentDetail(student = students.firstOrNull { it.id == id })
}
}
)
}
(Depending on the library version, navigateTo is a suspend function, as here, or a regular one; and navigateBack has similar variants. The IDE will tell you.)
How it behaves:
- Expanded window: both panes appear side by side. Selecting an item updates the detail pane.
- Compact window: one pane at a time.
navigateTo(Detail, id)shows the detail pane, and the system back gesture returns to the list because the scaffold's navigator handles back. - Resizing: when the window changes size, the scaffold re-evaluates how many panes fit and keeps the current destination.
For selection to survive configuration changes, the navigator's content key must be saveable: an Int or String works, an arbitrary class needs a custom saver or @Parcelize/@Serializable.
Supporting pane
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun LessonScreen(lesson: Lesson) {
val navigator = rememberSupportingPaneScaffoldNavigator()
NavigableSupportingPaneScaffold(
navigator = navigator,
mainPane = { AnimatedPane { LessonText(lesson) } },
supportingPane = { AnimatedPane { LessonNotes(lesson) } }
)
}
On a compact window the user sees the main pane, with a way (a button) to open the supporting pane, which replaces it; on wide windows both are visible.
Adaptive navigation again, with custom behaviour
import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold
@Composable
fun AppNav(content: @Composable () -> Unit) {
var current by rememberSaveable { mutableStateOf(Destination.Register) }
NavigationSuiteScaffold(
navigationSuiteItems = {
Destination.entries.forEach { dest ->
item(
selected = current == dest,
onClick = { current = dest },
icon = { Icon(dest.icon, contentDescription = null) },
label = { Text(dest.label) }
)
}
}
) {
content()
}
}
The suite picks bar, rail or drawer from the window's adaptive info. You can override that choice with a layoutType parameter when you have a reason, for example to keep a bottom bar on a medium window.
A foldable can be flat, half-open (like a laptop, book posture) or propped like a stand (tabletop posture). Jetpack WindowManager exposes the fold as a FoldingFeature with:
| Property | Meaning |
|---|---|
state |
FLAT or HALF_OPENED |
orientation |
HORIZONTAL or VERTICAL fold line |
occlusionType |
Whether the fold hides content (FULL) or not (NONE) |
isSeparating |
Whether the fold divides the display into two logical areas |
bounds |
The fold's rectangle in the window |
Detect tabletop posture
import androidx.window.layout.FoldingFeature
import androidx.window.layout.WindowInfoTracker
@OptIn(ExperimentalContracts::class)
fun isTableTopPosture(feature: FoldingFeature?): Boolean {
contract { returns(true) implies (feature != null) }
return feature?.state == FoldingFeature.State.HALF_OPENED &&
feature.orientation == FoldingFeature.Orientation.HORIZONTAL
}
class MainActivity : ComponentActivity() {
private val foldState = MutableStateFlow<FoldingFeature?>(null)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
WindowInfoTracker.getOrCreate(this@MainActivity)
.windowLayoutInfo(this@MainActivity)
.map { info -> info.displayFeatures.filterIsInstance<FoldingFeature>().firstOrNull() }
.collect { foldState.value = it }
}
}
setContent {
val fold by foldState.collectAsStateWithLifecycle()
AppTheme { VideoCallScreen(tabletop = isTableTopPosture(fold)) }
}
}
}
(ExperimentalContracts needs @OptIn(kotlin.contracts.ExperimentalContracts::class); the contract is optional and can be removed.)
Use the posture
In tabletop, show the video or content in the top half, and the controls in the bottom half, so the phone stands on a table and the controls lie flat under your hands:
@Composable
fun VideoCallScreen(tabletop: Boolean) {
if (tabletop) {
Column(Modifier.fillMaxSize()) {
VideoArea(Modifier.weight(1f)) // top half
ControlsArea(Modifier.weight(1f)) // bottom half
}
} else {
Box(Modifier.fillMaxSize()) {
VideoArea(Modifier.fillMaxSize())
ControlsArea(Modifier.align(Alignment.BottomCenter))
}
}
}
Rules for foldables:
- Never place critical content under the hinge if
occlusionType == FULL. - Keep state across fold and unfold; handle configuration changes yourself or use ViewModel and
rememberSaveable. - Test the book posture too: when the fold is vertical and separating, a two-pane layout with the divider on the fold looks natural.
- Do not assume a fold exists;
FoldingFeatureisnullon ordinary devices.
Do not lock orientation or resizing
On large screens, a manifest that forbids rotation or resizing makes the app letterboxed (a phone-sized column in the middle of the screen) because the system respects it. For apps targeting recent Android versions, the platform increasingly ignores such restrictions on large screens (the exact thresholds and behaviour changed across Android 16 and later; check the current docs). The robust approach is to avoid them:
<activity
android:name=".MainActivity"
android:exported="true"
android:resizeableActivity="true" />
<!-- Avoid: android:screenOrientation="portrait" and android:resizeableActivity="false" -->
Handle configuration changes properly: let the activity recreate and restore from the ViewModel and saved state, or declare android:configChanges only for cases like video playback where you handle them yourself.
Input beyond touch
Large-screen users have a keyboard, mouse, trackpad and stylus. Support:
- Keyboard shortcuts and focus order (Tab and arrow keys). Compose provides
Modifier.onKeyEvent,Modifier.onPreviewKeyEventandModifier.focusable(). - Hover states and tooltips for mouse users, right-click context menus.
- Drag and drop, text selection with
SelectionContainer. - Stylus input for notes and drawing.
@Composable
fun KeyboardAwareList(onSearch: () -> Unit, content: @Composable () -> Unit) {
Box(
Modifier.onPreviewKeyEvent { event ->
if (event.type == KeyEventType.KeyDown && event.isCtrlPressed && event.key == Key.F) {
onSearch()
true // consumed
} else false
}
) { content() }
}
Layout rules of thumb for wide windows
- Do not stretch: cap reading width (about 600 to 840 dp for text) using
Modifier.widthIn(max = 840.dp)with centred content. - Grow columns, not cards: use
LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 180.dp)). - Use space for context: show the detail beside the list rather than a bigger list.
- Reachability: on tablets, place frequent actions near edges where hands rest; avoid tiny controls in the far corners.
@Composable
fun StudentGrid(students: List<Student>) {
LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = 200.dp),
contentPadding = PaddingValues(16.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
items(students, key = { it.id }) { StudentCard(it) }
}
}
Test it
- Android Studio's resizable emulator (phone, foldable, tablet, desktop in one AVD) lets you resize the window live.
- Use Foldable and Pixel Tablet AVD definitions; the foldable emulator has virtual hinge sensors in the extended controls.
- In Compose previews, add
@PreviewScreenSizesor@Preview(device = Devices.TABLET)to preview several window sizes at once. - Large-screen quality checklists from the Android documentation list the tiers (Tier 3 basic, Tier 2 differentiated, Tier 1 optimised). Check the current wording on the developer site.
@PreviewScreenSizes
@Composable
fun RegisterPreview() {
AppTheme { RegisterScreen(sampleState, twoPane = false, onSelect = {}) }
}
(@PreviewScreenSizes renders several window shapes in the preview pane. For a preview to reflect real window classes, make the screen read the window size inside the composable rather than from a hard-coded parameter.)
Automated tests with a fixed window size
In Compose UI tests, you can test a layout at a known width by wrapping it in a Box(Modifier.width(900.dp)) or by parametrising the twoPane boolean, which is another reason to hoist it:
@get:Rule val rule = createComposeRule()
@Test
fun twoPane_showsListAndDetail() {
rule.setContent { AppTheme { RegisterScreen(sampleState.copy(selectedId = 1), twoPane = true, onSelect = {}) } }
rule.onNodeWithText("Adaeze Okoye").assertExists()
rule.onNodeWithText("Strong in Mathematics.").assertExists()
}
@Test
fun singlePane_showsOnlyList() {
rule.setContent { AppTheme { RegisterScreen(sampleState, twoPane = false, onSelect = {}) } }
rule.onNodeWithText("Strong in Mathematics.").assertDoesNotExist()
}
Exercise 1: Choose the layout
For each window, choose the navigation component and number of panes for a list-detail email app: (a) a phone in portrait, (b) the same phone with its app in a 500 dp split-screen pane, (c) a tablet in landscape, (d) an unfolded foldable at 700 dp width.
(a) Compact: bottom navigation bar, one pane (list, then detail). (b) Still compact (500 dp is below 600 dp), so the same as (a), even if the device is a big tablet. (c) Expanded: navigation rail or drawer, two panes (list plus detail). (d) Medium: navigation rail, and two panes if there is room (often yes at 700 dp with a narrow list); otherwise one. Decisions come from the window, not the device.
Exercise 2: Stretched text
A reading screen displays a long paragraph full-width on a 1,200 dp window. Fix it so the text is a comfortable width and centred.
@Composable
fun Reader(text: String) {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.TopCenter) {
Text(
text = text,
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier
.widthIn(max = 720.dp)
.padding(24.dp)
.verticalScroll(rememberScrollState())
)
}
}
Long lines are hard to read: aim for roughly 45 to 90 characters per line. widthIn(max = ...) leaves small windows alone and caps wide ones.
Exercise 3: Selection across a fold
The user selects a student on the phone's cover screen (compact), then unfolds the device (expanded). Describe what should happen and which two design choices make it work.
The unfolded layout should show the list with the same student highlighted and their detail on the right, with no reset. It works because (1) state is hoisted into the ViewModel (or the navigator with a saveable content key), so recreation after the configuration change keeps selectedId, and (2) the layout decision is a pure function of window size, so the same state renders as one pane or two.
Mistake #1: Branching on device type. Fix: branch on window size.
Mistake #2: Locking orientation or disabling resizing. Fix: support all window shapes.
Mistake #3: Calling the size-class calculation deep in the tree. Fix: hoist and pass down.
Mistake #4: Stretching phone layouts to tablets. Fix: use two panes, adaptive grids and widthIn.
Mistake #5: Losing state on resize or fold. Fix: ViewModel plus saveable state, ids as keys.
Mistake #6: Content under the hinge. Fix: read FoldingFeature bounds and occlusionType.
Mistake #7: Ignoring keyboard and mouse. Fix: focus order, shortcuts, hover.
Mistake #8: Assuming experimental adaptive APIs never change. Fix: pin versions and read release notes.
Mistake #9: Testing only on one phone. Fix: resizable emulator and preview sizes.
Mistake #10: Forgetting back handling in the one-pane case. Fix: use the scaffold navigator or BackHandler.
Question 1. What window width is the boundary between compact and medium?
600 dp (compact is below it; medium runs from 600 dp up to 840 dp).
Question 2. Name the three canonical layouts.
List-detail, supporting pane and feed.
Question 3. Why store selectedId rather than the selected object?
An id is small, saveable and always resolves against the current data, so selection survives recreation without stale copies.
Question 4. What does tabletop posture mean, and how would you design for it?
A foldable half-open with a horizontal fold. Put content in the top half and controls in the bottom half.
Question 5. Why avoid screenOrientation="portrait" and resizeableActivity="false"?
They cause letterboxing or are ignored on large screens, and they block multi-window, so the app feels broken on tablets and desktop-style windows.
Go deeper: official resources
- Jetpack Compose for Android developers, Pathway 5
- Build adaptive apps codelab
- Jetpack Compose course page
Project goal: Make the ClassRegister app adapt to phones, tablets and foldables.
Requirements:
- A
RegisterViewModelholding students andselectedId(as in Build 2). - A hoisted size class, with a bar on compact, rail on medium and rail or drawer on expanded (via
NavigationSuiteScaffold). - A list-detail layout using either the hand-built
RoworNavigableListDetailPaneScaffold. - A grid on a separate "Gallery" tab with
GridCells.Adaptive. - Text content capped with
widthIn. - A tabletop-aware screen for any media or camera feature (a stub is fine).
- No orientation locks;
resizeableActivity="true". - Two UI tests (single-pane and two-pane) and previews with
@PreviewScreenSizes. - Verify on the resizable emulator and write down what you saw at each size.
Stretch goal: add keyboard shortcut Ctrl+F to focus a search field, and a mouse hover highlight on rows.
A compact version of the root:
@Composable
fun ClassRegisterApp(vm: RegisterViewModel = viewModel()) {
val state by vm.uiState.collectAsStateWithLifecycle()
val info = currentWindowAdaptiveInfo()
val twoPane = info.windowSizeClass.windowWidthSizeClass != WindowWidthSizeClass.COMPACT
var tab by rememberSaveable { mutableStateOf(Tab.Register) }
NavigationSuiteScaffold(
navigationSuiteItems = {
Tab.entries.forEach {
item(
selected = tab == it,
onClick = { tab = it },
icon = { Icon(it.icon, contentDescription = null) },
label = { Text(it.label) }
)
}
}
) {
when (tab) {
Tab.Register -> RegisterScreen(state, twoPane, onSelect = vm::select)
Tab.Gallery -> StudentGrid(state.students)
}
}
}
Expected behaviour on the resizable emulator (example notes):
Phone (compact) bottom bar, list, tap student -> detail, back returns to list
Foldable unfolded rail on the left, list + detail side by side, selection kept
Tablet landscape rail, two panes, gallery shows 4-5 columns
Desktop window same, text capped at 720 dp and centred
Remember to check each result on a real device where you can; layouts are only proven by looking at them.
Open your finished project in Android Studio. Check that the .gitignore file the wizard created is in the project root. It keeps local.properties, build folders and keystores out of Git. Never commit passwords or signing keys.
Choose Git > Create Git Repository… (or VCS > Enable Version Control Integration in older versions), select the project folder, then open Git > Commit, tick every file, and write the message “Add Android lesson 58 project”.
On github.com click New repository, name it android-lesson-58-adaptive-apps-for-phones-tablets-and-folda, set it to Public, leave Add a README unticked, and click Create repository.
Back in Android Studio choose Git > Manage Remotes…, add the repository URL as origin, then choose Git > Push… and push your main branch. Prefer the terminal? Run git remote add origin <your-url> then git push -u origin main.
Add a README.md with a screenshot of your app running and a short description. Employers and clients read READMEs first. Your project is now part of your public portfolio. 🎉
Lesson 58 complete! 🎉
You covered:
- Lesson Introduction
- Prerequisite Concepts
- Conceptual Understanding: Why Adaptive?
- Step-by-Step Build 1: Dependencies and a Window Size Class
- Step-by-Step Build 2: A Master-Detail Register, Properly
- Step-by-Step Build 3: The Material 3 Adaptive Scaffolds
- Step-by-Step Build 4: Foldables and Postures
- Step-by-Step Build 5: Resizability, Input and Quality
- Guided Practice Exercises
- Common Mistakes (and How to Fix Them)
- Knowledge Check