Using LayoutBuilder and MediaQuery Widgets to Create Adaptive Layouts in Flutter

Flutter offers powerful tools to create adaptive and responsive layouts that adjust seamlessly to different screen sizes and orientations. Two essential widgets for building such layouts are LayoutBuilder and MediaQuery. Understanding and effectively using these widgets enables developers to create Flutter applications that provide an optimal user experience across various devices.

What are LayoutBuilder and MediaQuery?

  • LayoutBuilder: A widget that provides layout information about its parent. It allows you to make decisions about your layout based on the available space.
  • MediaQuery: Provides information about the current media, such as screen size, orientation, device pixel ratio, and more. It is an InheritedWidget, making it accessible from any child widget within its scope.

Why Use LayoutBuilder and MediaQuery?

  • Responsiveness: Create UIs that adapt to different screen sizes, orientations, and devices.
  • Dynamic Layout Adjustments: Change layout elements based on available space or device characteristics.
  • Accessibility: Ensure your app is accessible by adapting to various display settings and user preferences.

How to Implement Adaptive Layouts Using LayoutBuilder

The LayoutBuilder widget is particularly useful when you need to make decisions about your layout based on the available space provided by its parent.

Step 1: Basic Usage of LayoutBuilder

Here’s a basic example of how to use LayoutBuilder:


import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('LayoutBuilder Example')),
        body: LayoutBuilder(
          builder: (BuildContext context, BoxConstraints constraints) {
            if (constraints.maxWidth > 600) {
              return WideLayout();
            } else {
              return NarrowLayout();
            }
          },
        ),
      ),
    );
  }
}

class WideLayout extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('Wide Screen Layout'),
    );
  }
}

class NarrowLayout extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('Narrow Screen Layout'),
    );
  }
}

In this example:

  • The LayoutBuilder determines whether to display WideLayout or NarrowLayout based on the maxWidth provided by its constraints.
  • If the available width is greater than 600 logical pixels, WideLayout is displayed; otherwise, NarrowLayout is used.

Step 2: Using LayoutBuilder for Dynamic GridView

You can dynamically adjust the number of columns in a GridView based on available space:


import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Dynamic GridView')),
        body: LayoutBuilder(
          builder: (BuildContext context, BoxConstraints constraints) {
            int crossAxisCount = (constraints.maxWidth / 150).floor(); // Adjust 150 based on item width
            return GridView.builder(
              gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: crossAxisCount > 0 ? crossAxisCount : 1,
                crossAxisSpacing: 10,
                mainAxisSpacing: 10,
                childAspectRatio: 1,
              ),
              padding: EdgeInsets.all(10),
              itemCount: 20,
              itemBuilder: (context, index) {
                return Container(
                  color: Colors.blue,
                  child: Center(
                    child: Text(
                      'Item $index',
                      style: TextStyle(color: Colors.white),
                    ),
                  ),
                );
              },
            );
          },
        ),
      ),
    );
  }
}

In this example:

  • The number of columns (crossAxisCount) in the GridView is determined dynamically based on the available width.
  • For every 150 logical pixels of width, an additional column is added, ensuring items remain appropriately sized across different screen sizes.

How to Implement Adaptive Layouts Using MediaQuery

The MediaQuery widget provides information about the device’s screen size, orientation, pixel density, and more. It’s useful when you need to make layout decisions based on these device-specific properties.

Step 1: Accessing Screen Size Using MediaQuery

Here’s how you can access the screen size using MediaQuery:


import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('MediaQuery Example')),
        body: ScreenDetails(),
      ),
    );
  }
}

class ScreenDetails extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final screenSize = MediaQuery.of(context).size;
    final screenWidth = screenSize.width;
    final screenHeight = screenSize.height;

    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text('Screen Width: $screenWidth'),
          Text('Screen Height: $screenHeight'),
        ],
      ),
    );
  }
}

In this example:

  • We access the screen size using MediaQuery.of(context).size.
  • The ScreenDetails widget displays the screen width and height.

Step 2: Using MediaQuery for Orientation-Based Layouts

You can adapt the layout based on the device’s orientation:


import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Orientation Layout')),
        body: OrientationLayout(),
      ),
    );
  }
}

class OrientationLayout extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final orientation = MediaQuery.of(context).orientation;

    return orientation == Orientation.portrait
        ? PortraitLayout()
        : LandscapeLayout();
  }
}

class PortraitLayout extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('Portrait Mode'),
    );
  }
}

class LandscapeLayout extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('Landscape Mode'),
    );
  }
}

In this example:

  • We determine the device’s orientation using MediaQuery.of(context).orientation.
  • The OrientationLayout widget displays different layouts for portrait and landscape orientations.

Best Practices for Adaptive Layouts

  • Combine LayoutBuilder and MediaQuery: Use both widgets to make comprehensive decisions about layout and device characteristics.
  • Use Flexible Widgets: Utilize widgets like Expanded, Flexible, and FractionallySizedBox to create dynamic layouts.
  • Design for the Smallest Screen First: Start with the layout for the smallest supported screen size and then adapt it for larger screens.
  • Test on Multiple Devices: Ensure your app looks good on a variety of devices and screen sizes.

Conclusion

LayoutBuilder and MediaQuery are powerful tools for building adaptive layouts in Flutter. By understanding and effectively using these widgets, developers can create Flutter applications that provide an optimal user experience across various devices and orientations. Combining these widgets with flexible layout components results in robust, responsive, and accessible user interfaces.