Responsive Flutter Layouts: Mastering MediaQuery, LayoutBuilder, and Flex for All Screen Sizes
The Flutter news you actually need
No spam, ever. Unsubscribe in one click.
Building Flutter UIs That Flex: Beyond Basic MediaQuery
We’ve all been there. You build a beautiful app on your preferred device, only to find it looks broken on a tablet, awkward on a desktop, or cramped on a small phone. The classic solution is to reach for MediaQuery.of(context).size.width and sprinkle conditional logic everywhere. But this often leads to a codebase littered with magic number breakpoints, widgets that rebuild unnecessarily, and font sizes that don’t scale appropriately.
The core issue is treating screen size as a one-dimensional problem. True responsiveness isn’t just about width; it’s about understanding the available space a widget can use and making intelligent layout decisions accordingly.
Let’s move beyond the boilerplate and explore three powerful tools that, when used correctly, create robust, adaptive layouts.
1. MediaQuery: For Global Context, Not Local Layouts
MediaQuery is your window into the device’s characteristics: screen size, padding, text scaling, and more. The common pitfall is overusing it for direct layout decisions inside every widget.
A better practice is to use it for high-level, global constraints or when you need information about the device, not just available space. A key improvement is using MediaQuery.sizeOf(context), which is more readable and null-safe than the older .of(context).size pattern.
Use it for:
- Setting app-wide padding (like respecting the system status bar).
- Adjusting to platform-specific insets (notch, bottom safe area).
- Reacting to system text scale factor.
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Good use: Getting safe areas for a scaffold.
final padding = MediaQuery.viewPaddingOf(context);
final screenSize = MediaQuery.sizeOf(context);
return Scaffold(
appBar: AppBar(title: const Text('Responsive App')),
body: SafeArea(
// Use padding to ensure content is not hidden.
minimum: EdgeInsets.only(top: padding.top),
child: MyAdaptiveContent(),
),
);
}
}
2. LayoutBuilder: The Gold Standard for Local Adaptability
This is your most important tool. LayoutBuilder tells a widget exactly how much space its parent has allocated for it right now. This is fundamentally different from the screen size. A widget might be in a sidebar, a dialog, or a column, and LayoutBuilder respects that.
Use it for:
- Changing a widget’s internal layout based on the space it’s given.
- Creating components that adapt to their container, not the screen.
class MyAdaptiveContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
// constraints.maxWidth is the width available TO THIS WIDGET.
if (constraints.maxWidth > 600) {
// Wide layout: place items side-by-side.
return Row(
children: [
Expanded(child: ProductDetailsPanel()),
Expanded(child: ShoppingCartPanel()),
],
);
} else {
// Narrow layout: stack items vertically.
return Column(
children: [
ProductDetailsPanel(),
ShoppingCartPanel(),
],
);
}
},
);
}
}
This widget is now self-contained and reusable anywhere. It will adapt whether it’s placed full-screen on a phone or inside a panel on a desktop app.
3. Flex, Expanded, and Flexible: Let the Framework Do the Math
When you have a row or column where children should share space proportionally, don’t manually calculate widths. Use the Flex family (Row and Column are Flex widgets) with Expanded and Flexible.
Expanded: Makes a child fill the available space in the main axis. Useflexproperty to assign proportions (default is 1).Flexible: Similar but allows the child to be smaller than the allocated space (usingfit: FlexFit.loose).
Use it for:
- Creating fluid, proportion-based layouts (e.g., a 70/30 split).
- Ensuring widgets fill remaining space without overflow errors.
Widget build(BuildContext context) {
return Row(
children: [
// This panel will take 2 portions of the space.
Expanded(
flex: 2,
child: MainArticleView(),
),
// This panel will take 1 portion.
Expanded(
flex: 1,
child: RelatedArticlesSidebar(),
),
],
);
}
Putting It All Together: A Responsive Strategy
Here’s a practical approach:
- Start with
LayoutBuilderat the top of your UI sections. Decide on major layout changes (like from column to row) based on the availableconstraints. - Use
Flexwidgets internally to manage space distribution within those layouts. Avoid hard-coded widths when you can useExpanded. - Reserve
MediaQueryfor truly global concerns like safe areas, orientation, or platform-specific adjustments. Consider using it once high up in your widget tree and passing relevant values down.
Common Mistake to Avoid: Responsive Text Sizes
Don’t scale text directly with MediaQuery.sizeOf(context).width. A headline that looks good on a tablet will be gigantic on a desktop window. Instead, use MediaQuery.textScaleFactorOf(context) to respect the user’s system accessibility settings, and define a sensible set of TextStyle themes in your ThemeData that work across breakpoints. For more granular control, consider a package like auto_size_text or calculate size based on the local constraints from a LayoutBuilder.
By thinking in terms of available space (LayoutBuilder) and proportional distribution (Flex) rather than just screen pixels (MediaQuery), you build components that are inherently adaptable. This leads to cleaner code and UIs that feel at home on any device.
This blog is produced with the assistance of AI by a human editor. Learn more
Related Posts
Flutter's Hidden Gem: Building Powerful Linux Desktop Apps with Ease
Flutter is gaining traction as a robust framework for Linux desktop development, offering a smoother experience compared to traditional toolkits like GTK and Qt. This post will explore why Flutter excels for Linux apps, highlight its advantages, and provide practical insights for developers looking to leverage Flutter for their desktop projects.
Simplifying Flutter Desktop Deployment: Signing and Distribution for Windows
Deploying Flutter desktop apps on Windows can be challenging, especially regarding code signing and preventing Windows from blocking installations. This post will guide developers through the process of signing their Flutter desktop applications, exploring alternatives to the Microsoft Store like creating executable installers, and covering the necessary steps to ensure a smooth user experience.
Mastering Flutter Tooling: Streamlining SDK Management and Installation on Windows
Developers frequently voice frustrations about Flutter SDK version management and installation processes, particularly on Windows. This post will explore best practices for managing multiple Flutter versions using tools like FVM, discuss integrating Flutter with package managers like Winget for a smoother Windows setup, and offer strategies to ensure a consistent development environment across projects and teams.