Use MediaQuery to Make Your App Responsive in Flutter
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
-
Screen Size Considerations
- Use relative sizes instead of fixed dimensions
- Consider different aspect ratios
- Test on various device sizes
-
Layout Guidelines
- Keep UI elements proportional
- Use flexible widgets (Expanded, Flexible)
- Implement proper constraints
-
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.