Back to Posts

Use MediaQuery to Make Your App Responsive in Flutter

14 min read

Creating responsive applications that work well across different screen sizes and orientations is crucial for modern app development. This guide will show you how to use MediaQuery effectively to create adaptive layouts in Flutter.

Understanding MediaQuery Basics

MediaQuery provides information about the current app's window dimensions and settings:

class MediaQueryDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Get the screen size information
    final screenSize = MediaQuery.of(context).size;
    final screenWidth = screenSize.width;
    final screenHeight = screenSize.height;
    
    // Get other useful information
    final orientation = MediaQuery.of(context).orientation;
    final padding = MediaQuery.of(context).padding;
    final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;

    return Column(
      children: [
        Text('Screen Width: $screenWidth'),
        Text('Screen Height: $screenHeight'),
        Text('Orientation: $orientation'),
        Text('Device Pixel Ratio: $devicePixelRatio'),
        Text('Top Padding: ${padding.top}'),
        Text('Bottom Padding: ${padding.bottom}'),
      ],
    );
  }
}

Creating Responsive Layouts

Implement layouts that adapt to different screen sizes:

class ResponsiveLayout extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final screenWidth = MediaQuery.of(context).size.width;
    
    // Define breakpoints
    const tabletBreakpoint = 768.0;
    const desktopBreakpoint = 1200.0;

    return LayoutBuilder(
      builder: (context, constraints) {
        if (screenWidth >= desktopBreakpoint) {
          return _buildDesktopLayout();
        } else if (screenWidth >= tabletBreakpoint) {
          return _buildTabletLayout();
        } else {
          return _buildMobileLayout();
        }
      },
    );
  }

  Widget _buildDesktopLayout() {
    return Row(
      children: [
        Expanded(
          flex: 1,
          child: _buildSidebar(),
        ),
        Expanded(
          flex: 3,
          child: _buildMainContent(),
        ),
        Expanded(
          flex: 1,
          child: _buildDetailsPanel(),
        ),
      ],
    );
  }

  Widget _buildTabletLayout() {
    return Row(
      children: [
        Expanded(
          flex: 1,
          child: _buildSidebar(),
        ),
        Expanded(
          flex: 2,
          child: _buildMainContent(),
        ),
      ],
    );
  }

  Widget _buildMobileLayout() {
    return Column(
      children: [
        _buildMainContent(),
      ],
    );
  }

  Widget _buildSidebar() {
    return Container(
      color: Colors.grey[200],
      child: ListView(
        children: [
          ListTile(title: Text('Menu Item 1')),
          ListTile(title: Text('Menu Item 2')),
          ListTile(title: Text('Menu Item 3')),
        ],
      ),
    );
  }

  Widget _buildMainContent() {
    return Container(
      color: Colors.white,
      child: Center(
        child: Text('Main Content'),
      ),
    );
  }

  Widget _buildDetailsPanel() {
    return Container(
      color: Colors.grey[100],
      child: Column(
        children: [
          Text('Details'),
          Divider(),
          Text('Additional Information'),
        ],
      ),
    );
  }
}

Handling Device Orientation

Adapt your UI based on device orientation:

class OrientationResponsiveWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return OrientationBuilder(
      builder: (context, orientation) {
        return orientation == Orientation.portrait
            ? _buildPortraitLayout(context)
            : _buildLandscapeLayout(context);
      },
    );
  }

  Widget _buildPortraitLayout(BuildContext context) {
    return Column(
      children: [
        Container(
          height: MediaQuery.of(context).size.height * 0.4,
          child: _buildImageSection(),
        ),
        Expanded(
          child: _buildContentSection(),
        ),
      ],
    );
  }

  Widget _buildLandscapeLayout(BuildContext context) {
    return Row(
      children: [
        Container(
          width: MediaQuery.of(context).size.width * 0.5,
          child: _buildImageSection(),
        ),
        Expanded(
          child: _buildContentSection(),
        ),
      ],
    );
  }

  Widget _buildImageSection() {
    return Container(
      color: Colors.blue[100],
      child: Center(
        child: Icon(
          Icons.image,
          size: 100,
          color: Colors.blue,
        ),
      ),
    );
  }

  Widget _buildContentSection() {
    return Container(
      padding: EdgeInsets.all(16.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            'Article Title',
            style: TextStyle(
              fontSize: 24,
              fontWeight: FontWeight.bold,
            ),
          ),
          SizedBox(height: 16),
          Text(
            'Article content goes here with a detailed description...',
            style: TextStyle(fontSize: 16),
          ),
        ],
      ),
    );
  }
}

Responsive Grid Layout

Create a grid that adapts to screen size:

class ResponsiveGrid extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        final width = constraints.maxWidth;
        final itemCount = 20;
        
        // Calculate columns based on width
        int crossAxisCount;
        if (width > 1200) {
          crossAxisCount = 6;
        } else if (width > 900) {
          crossAxisCount = 4;
        } else if (width > 600) {
          crossAxisCount = 3;
        } else {
          crossAxisCount = 2;
        }

        return GridView.builder(
          gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: crossAxisCount,
            childAspectRatio: 1.0,
            crossAxisSpacing: 8.0,
            mainAxisSpacing: 8.0,
          ),
          itemCount: itemCount,
          itemBuilder: (context, index) {
            return Card(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Icon(Icons.photo, size: 48),
                  SizedBox(height: 8),
                  Text('Item $index'),
                ],
              ),
            );
          },
        );
      },
    );
  }
}

Responsive Text Sizing

Implement text that scales with screen size:

class ResponsiveText extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Get the screen width
    final screenWidth = MediaQuery.of(context).size.width;
    
    // Calculate responsive font sizes
    final baseFontSize = screenWidth * 0.04;
    final titleFontSize = baseFontSize * 1.5;
    final subtitleFontSize = baseFontSize * 1.2;
    final bodyFontSize = baseFontSize;

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          'Responsive Title',
          style: TextStyle(
            fontSize: titleFontSize,
            fontWeight: FontWeight.bold,
          ),
        ),
        SizedBox(height: 8),
        Text(
          'Responsive Subtitle',
          style: TextStyle(
            fontSize: subtitleFontSize,
            fontWeight: FontWeight.w500,
          ),
        ),
        SizedBox(height: 16),
        Text(
          'This is responsive body text that will scale based on the screen width. '
          'It should remain readable across different device sizes.',
          style: TextStyle(
            fontSize: bodyFontSize,
          ),
        ),
      ],
    );
  }
}

Best Practices

  1. Screen Size Considerations

    • Use relative sizes instead of fixed dimensions
    • Consider different aspect ratios
    • Test on various device sizes
  2. Layout Guidelines

    • Keep UI elements proportional
    • Use flexible widgets (Expanded, Flexible)
    • Implement proper constraints
  3. Performance

    • Cache MediaQuery values when possible
    • Avoid unnecessary rebuilds
    • Use const constructors where appropriate

Common Issues and Solutions

Issue 1: Keyboard Adjustments

Handle keyboard appearance properly:

class KeyboardAwareWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final viewInsets = MediaQuery.of(context).viewInsets;
    final keyboardHeight = viewInsets.bottom;

    return Scaffold(
      body: Column(
        children: [
          Expanded(
            child: Content(),
          ),
          Container(
            padding: EdgeInsets.only(bottom: keyboardHeight),
            child: TextField(
              decoration: InputDecoration(
                hintText: 'Type something...',
              ),
            ),
          ),
        ],
      ),
    );
  }
}

Issue 2: Safe Area Handling

Ensure content respects device notches and system UI:

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

    return Container(
      padding: EdgeInsets.only(
        top: padding.top,
        bottom: padding.bottom,
        left: padding.left,
        right: padding.right,
      ),
      child: Content(),
    );
  }
}

Conclusion

MediaQuery is a powerful tool for creating responsive Flutter applications. Key takeaways:

  • Use MediaQuery to access device metrics and settings
  • Implement responsive layouts based on screen size
  • Handle different orientations appropriately
  • Create adaptive grids and text sizing
  • Consider safe areas and keyboard adjustments

With these implementations, you can create Flutter applications that provide a great user experience across all device sizes and orientations.