Dart Tutorial

Flutter Lesson 58 of 83 4 min read

MediaQuery and LayoutBuilder in Flutter

Learn to read the screen size with MediaQuery and the available space with LayoutBuilder, and when to use each for responsive Flutter UI.

On this page

Two tools answer the question “how much room do I have?” They answer it differently, and the difference matters.

MediaQueryLayoutBuilder
Tells youAbout the whole screen and device settingsThe space this widget has been given
Use forApp-level decisions, keyboard, notches, text scaleComponent-level layout decisions

MediaQuery #

import 'package:flutter/material.dart';

void main() => runApp(const MaterialApp(home: InfoPage()));

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

  @override
  Widget build(BuildContext context) {
    final size = MediaQuery.sizeOf(context);
    final orientation = MediaQuery.orientationOf(context);
    final padding = MediaQuery.paddingOf(context);
    final brightness = MediaQuery.platformBrightnessOf(context);

    return Scaffold(
      body: Center(
        child: Text(
          'Size: ${size.width.round()} x ${size.height.round()}\n'
          'Orientation: ${orientation.name}\n'
          'Top inset: ${padding.top.round()}\n'
          'System theme: ${brightness.name}',
          textAlign: TextAlign.center,
          style: const TextStyle(fontSize: 20),
        ),
      ),
    );
  }
}

Resize the window, or rotate the device, and the widget rebuilds with new values.

AccessorGives
MediaQuery.sizeOf(context)Screen (or window) width and height in logical pixels
MediaQuery.orientationOf(context)Portrait or landscape
MediaQuery.paddingOf(context)Space taken by notches, status bar and gesture bar
MediaQuery.viewInsetsOf(context)Space covered by the keyboard
MediaQuery.textScalerOf(context)The user’s text size setting
MediaQuery.platformBrightnessOf(context)The device’s light or dark setting
MediaQuery.devicePixelRatioOf(context)Physical pixels per logical pixel
MediaQuery.disableAnimationsOf(context)Whether the user asked for reduced motion

Use these specific accessors, not MediaQuery.of(context).size. With sizeOf, the widget rebuilds only when the size changes. With of(context), it rebuilds when anything changes, including each frame of the keyboard sliding up.

Logical pixels #

All sizes in Flutter are logical pixels, not physical ones. A 100-pixel box is about the same physical size on every screen, whatever its resolution. A typical phone is 360 to 430 logical pixels wide, a tablet 600 to 1000, a desktop window more.

LayoutBuilder #

LayoutBuilder gives the constraints from the parent: how big this particular widget may be.

import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        const image = SizedBox(width: 120, height: 120, child: ColoredBox(color: Colors.teal));
        const details = Padding(
          padding: EdgeInsets.all(12),
          child: Text('Trail backpack\nRs. 3,499'),
        );

        // Wide enough: image beside the text. Otherwise: image above.
        if (constraints.maxWidth > 300) {
          return const Row(children: [image, Expanded(child: details)]);
        }
        return const Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [image, details],
        );
      },
    );
  }
}

void main() {
  runApp(
    const MaterialApp(
      home: Scaffold(
        body: Row(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Expanded(flex: 3, child: Card(child: ProductTile())),
            Expanded(flex: 1, child: Card(child: ProductTile())),
          ],
        ),
      ),
    ),
  );
}

Both tiles are on the same screen, but they get different widths and choose different layouts. MediaQuery could not do this, because both would see the same screen size.

Which one to use #

Prefer LayoutBuilder for components. A card, a form or a list should adapt to the space it has. Then it works in a full-width phone screen, in half of a tablet, in a dialog and in a side panel, with no changes.

Use MediaQuery for:

  • Top-level decisions: bottom navigation or a side rail?
  • The keyboard: viewInsetsOf(context).bottom.
  • Safe areas, text scale and reduced motion.

Avoid sizing by percentage of the screen #

A common beginner habit:

// Fragile
Container(width: MediaQuery.sizeOf(context).width * 0.8, height: MediaQuery.sizeOf(context).height * 0.3)

This produces a tiny box on a small phone and a huge one on a tablet, and text inside may not fit either. Let content size itself, and use the layout tools:

GoalUse
Fill the remaining spaceExpanded
Share space in proportionExpanded(flex: ...)
Never exceed a widthConstrainedBox(constraints: BoxConstraints(maxWidth: 600))
A fraction of the parentFractionallySizedBox(widthFactor: 0.8)
Keep a shapeAspectRatio(aspectRatio: 16 / 9)
Flow onto more linesWrap
Shrink to fitFittedBox, Flexible

Do not scale everything with the screen #

Some packages scale every font and size by the screen width. The result is that a tablet shows the same content as a phone, only bigger, which wastes the extra space. A larger screen should show more, not larger. Use breakpoints to change the layout, as the next lesson shows.

Text scale #

Users can enlarge text in their settings, sometimes to double size. Flutter applies this to every Text automatically. Your part is to not break it: avoid fixed heights around text, let text wrap, and test with large text enabled.

Try it yourself #

Build a ProfileCard that uses LayoutBuilder: with less than 400 pixels of width it stacks an avatar above the name and bio, and with more it places the avatar on the left. Show it twice on one screen at different widths.

Practise in the playground Updated by Santosh Adhikari