skip to content

Responsive & Adaptive UI

Responsive layouts read a widget's space with LayoutBuilder or the screen with MediaQuery.sizeOf and switch at breakpoints; adaptive ones also vary by platform. Interviewers probe which to use.

part ofFlutteroverview, primer and where to startread it →
on this pageshow

explore

questions

6

In Flutter, what does the SafeArea widget do, and which of its parameters let you tune the padding it applies?

level: juniorimportance: must knowfreq 58%

answer

  1. notch, status bar, home indicator
  2. reads MediaQuery padding
  3. wraps the child in Padding
  4. removes padding for descendants
  5. minimum is a per-side max

basics

~20 s

SafeArea pads its child by the MediaQuery padding so system UI such as the status bar, notch and home indicator does not cover it. Per-side booleans switch sides off, minimum sets a floor, and maintainBottomViewPadding keeps the bottom inset while the keyboard is open.

solid answer

~40 s

`SafeArea` reads `MediaQuery.paddingOf(context)` — the parts of the display partly covered by system UI, like the status bar, a camera cutout or the home indicator — and wraps its child in a `Padding` of that size. It then calls `MediaQuery.removePadding` for its child, so a nested `SafeArea` does not pad twice. The `left`, `top`, `right` and `bottom` flags, all `true` by default, choose which sides to avoid. `minimum` sets a floor: on each side the **greater** of the minimum and the system padding wins, not their sum. `maintainBottomViewPadding` uses the bottom `viewPadding` instead of `padding`, so content does not jump when the keyboard opens. It matters more since Flutter 3.27, because Android apps targeting SDK 35 draw edge-to-edge by default.

code

dart · 21 lines
dart
import 'package:flutter/material.dart';

class NoteEditorScreen extends StatelessWidget {
  const NoteEditorScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        minimum: const EdgeInsets.symmetric(horizontal: 16),
        maintainBottomViewPadding: true,
        child: Column(
          children: <Widget>[
            const Expanded(child: TextField(maxLines: null, expands: true)),
            FilledButton(onPressed: () {}, child: const Text('Save note')),
          ],
        ),
      ),
    );
  }
}

go deeper

for a junior

Know that SafeArea insets content from the status bar, notch and home indicator, and name its four side flags and minimum.

for a middle

Explain that it reads MediaQuery padding, removes it for descendants so nesting is safe, and that minimum is a per-side maximum rather than a sum.

for a senior

Show when not to use it: Scaffold with an AppBar, edge-to-edge backgrounds, keyboard jumps fixed by maintainBottomViewPadding, and SliverSafeArea in scroll views.

for a principal

Set a team convention for where insets are handled, such as in shared scaffolds, so edge-to-edge changes on new Android releases are absorbed in one place.

## The problem SafeArea solves Modern phones draw an app **edge to edge**: the status bar, a notch or camera cutout, rounded corners and the gesture bar or home indicator all sit on top of the app's pixels. Since Flutter 3.27, apps target Android 15 (SDK 35) by default, and Android draws such apps edge to edge. On Android 16 an app cannot opt out. On iOS the notch and home indicator have long worked this way. Without care, a title slides under the status bar and a bottom button sits under the home indicator. Flutter reports these regions through `MediaQueryData.padding`: the parts of the display **partly** covered by system UI. `SafeArea` is the widget that turns that data into layout. ## What it does, step by step 1. It reads `MediaQuery.paddingOf(context)`. 2. If `maintainBottomViewPadding` is true, it replaces the bottom value with `MediaQuery.viewPaddingOf(context).bottom`. 3. For each side it computes `max(sideEnabled ? padding.side : 0, minimum.side)`. 4. It wraps the child in a `Padding` with those values. 5. It wraps the child in `MediaQuery.removePadding` for the sides it avoided, so descendants see zero padding there. Step 5 is why nesting is safe: an inner `SafeArea` sees zero padding on already-handled sides and adds nothing. ## The parameters | Parameter | Default | Effect | |---|---|---| | `left`, `top`, `right`, `bottom` | `true` | whether to avoid system UI on that side | | `minimum` | `EdgeInsets.zero` | per-side floor; the larger of it and the system padding is used | | `maintainBottomViewPadding` | `false` | keep the bottom `viewPadding` while the keyboard covers it | | `child` | required | the content to inset | Two details trip people up: - `minimum` applies **even on a side you disabled**. `SafeArea(top: false, minimum: EdgeInsets.all(16))` still pads the top by 16, because the calculation is `max(0, 16)`. - `minimum` is a **max, not a sum**. With a 24-pixel status bar and `minimum: EdgeInsets.all(16)`, the top padding is 24, not 40. ## Why maintainBottomViewPadding exists `padding` is derived as `max(0, viewPadding - viewInsets)`. When the software keyboard opens, `viewInsets.bottom` becomes the keyboard's height, so `padding.bottom` drops to zero: the keyboard now covers the home-indicator area. A `SafeArea` around a layout with flexible children then loses its bottom padding, and the content visibly jumps. Setting `maintainBottomViewPadding: true` keeps the value from `viewPadding`, which the keyboard does not change. ## Where to put it - `Scaffold` already handles part of this: when it has an `appBar`, the app bar pads for the status bar and the body's top padding is removed. Wrapping the whole `Scaffold` in `SafeArea` then leaves a blank band where the app bar colour should extend. - A body **without** an app bar, a full-screen image viewer, or a custom bottom bar usually does need `SafeArea`. - Inside a `CustomScrollView`, use `SliverSafeArea`, the sliver counterpart. - Sometimes you want only one side: `SafeArea(top: false, child: ...)` for a bottom action bar whose background should run under the status bar. ## Common mistakes - Hard-coding `EdgeInsets.only(top: 24)` for the status bar, which breaks on every device with a different inset. - Wrapping every screen's root in `SafeArea` without checking what `Scaffold` and `AppBar` already do. - Expecting `SafeArea` to avoid the keyboard; keyboard avoidance is driven by `viewInsets`, not `padding`.

  • Why can wrapping a whole Scaffold that has an AppBar in SafeArea look wrong?
    `AppBar` already pads its content for the status bar and extends its background underneath it. An outer `SafeArea` pushes the whole `Scaffold` down, leaving a band above the app bar filled with the window background instead of the app bar colour. Put `SafeArea` inside the body, or on the sides that actually need it.
  • What does SafeArea(top: false, minimum: EdgeInsets.all(16)) apply at the top?
    16 logical pixels. Each side is `max(enabled ? padding : 0, minimum)`, so disabling the top only drops the system padding from the comparison; the minimum still applies.

saying these in an interview costs you the question

  • SafeArea adds the minimum to the system padding on each side.
  • Nesting SafeArea widgets doubles the padding.
  • SafeArea keeps text fields clear of the software keyboard.
  • Hard-coding a 24-pixel top padding is equivalent to SafeArea.
  • Setting top to false also turns off the minimum on that side.
open as a page

In Flutter, when should a responsive widget read LayoutBuilder constraints instead of MediaQuery.sizeOf, and what does each one measure?

level: middleimportance: must knowfreq 65%

basics

~20 s

MediaQuery.sizeOf returns the app window's size in logical pixels; LayoutBuilder gives its builder the constraints the parent passes at that exact spot. Use sizeOf for app-level layout decisions and LayoutBuilder for a reusable widget that adapts to its own slot.

open as a page

In Flutter's MediaQueryData, how do padding, viewPadding and viewInsets differ, and what happens to each when the software keyboard opens?

level: middleimportance: should knowfreq 40%

basics

~20 s

viewPadding is the area partly covered by system UI such as a notch or home indicator; viewInsets is the area fully covered, typically by the keyboard; padding is max(0, viewPadding - viewInsets). Opening the keyboard raises viewInsets.bottom and can drop padding.bottom to zero.

open as a page

In Flutter, what does OrientationBuilder actually measure, and why do the docs advise against switching whole layouts on orientation?

level: middleimportance: should knowfreq 38%

basics

~20 s

OrientationBuilder compares its parent's maxWidth with maxHeight: wider is landscape, anything else portrait. Orientation says nothing about how much room exists — a landscape phone can be narrower than a portrait tablet — so layouts should branch on width with breakpoints.

open as a page

How would you build one Flutter codebase so a note-taking app shows a list-detail split on tablets but a single pane with a separate detail view on phones?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Measure the window with MediaQuery.sizeOf or widthOf at the shell, branch on a width breakpoint such as 600, and render either a Row of list and detail or a single pane. Keep the selected note above the branch so resizing preserves it.

open as a page

On a foldable Android device, how does a Flutter app locate a hinge or fold, and how do you keep content from straddling it?

level: seniorimportance: nice to knowfreq 15%

basics

~20 s

MediaQuery.displayFeaturesOf returns DisplayFeature objects with logical-pixel bounds, a type (fold, hinge, cutout) and a posture state; the list is populated only on Android. Split panes at a hinge's bounds, or wrap content in DisplayFeatureSubScreen to confine it to one side.

open as a page