Responsive UIs for All Platforms
Flutter originally started as a framework with a primary focus on creating high-quality native apps for iOS and Android smartphones. It quickly evolved to support platforms beyond smartphones, such as tablets, web browsers, and desktop applications. With such a variety of devices to support, it has become important for developers to learn how to create responsive user interfaces (UIs) that look great on every screen.
Get Aly Route’s stories in your inbox
Join Medium for free to get updates from this writer.
It is important to keep in mind that each platform has its own unique design guidelines and user experience expectations. Flutter brings with it all the tools needed to create responsive apps that work across a wide range of devices, screen sizes, and orientations.
User’s device with MediaQuery
MediaQuery is a utility widget that provides information about the device and its constraints. It allows you to query various properties of the device, such as its orientation, screen size, pixel density, and more.
When talking about screen size and pixels, there are a couple of concepts that we need to understand. Physical pixels, also known as device pixels, is a measure of the number of actual pixels on the display. This can vary a lot based not only on the actual screen size but also on pixel density. Pixel density is often measured in inches and referred to as pixel per inch (PPI). The higher the PPI, the higher the resolution and the better the display quality. On the other hand, there are logical pixels which are roughly 96 physical pixels per inch. This concept is also known as device-independent pixels. It allows for creating an app UI without dealing with pixel density. In Flutter, we work with logical pixels.
Here are some of the most useful properties on MediaQuery we can use:
- size: The physical size of the device’s screen in logical pixels
- devicePixelRatio: The number of physical pixels per logical pixel
- orientation: The orientation of the device (portrait or landscape)
- padding: The amount of padding applied to the screen, such as the status bar and navigation bar
- textScaleFactor: The scaling factor applied to text based on the device’s accessibility settings
The MediaQuery widget is included in the widget tree when using MaterialApp, CupertinoApp, or WidgetsApp in your Flutter app. This allows you to access the properties of the device and its constraints throughout your app.
void main() {
runApp(
MaterialApp(
home: Scaffold(
body: ResponsiveLayoutExample(),
),
),
);
}
class ResponsiveLayoutExample extends StatelessWidget {
const ResponsiveLayoutExample({super.key});
@override
Widget build(BuildContext context) {
final screenSize = MediaQuery.of(context).size;
return GridView.count(
crossAxisCount: _getCrossAxisCount(screenSize.width),
children: List.generate(
12,
(index) => Container(
margin: const EdgeInsets.all(8),
alignment: Alignment.center,
color: Colors.green,
child: Text('Item ${index + 1}'),
),
),
);
}
int _getCrossAxisCount(double screenWidth) {
if (screenWidth >= 1200) {
return 4;
} else if (screenWidth >= 800) {
return 3;
} else if (screenWidth >= 600) {
return 2;
} else {
return 1;
}
}
}we used MediaQuery to retrieve the screen size and determine the number of columns in the grid based on the screen width. The _getCrossAxisCount method returns 4 columns for screens wider than 1200 pixels, 3 columns for screens between 800 and 1200 pixels, 2 columns for screens between 600 and 800 pixels, and 1 column for screens narrower than 600 pixels. We have also used a GridView widget here, which is an easy way to show a list of items in a grid format.
Creating adaptive layouts
When we want to create dynamic layouts based on the available size or the device’s orientation.
- LayoutBuilder is a widget that allows you to create layouts that are responsive and adapt to different screen sizes.
void main() {
runApp(
MaterialApp(
home: Scaffold(
body: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints){
if (constraints.maxWidth > 600) {
return DesktopLayout();
} else {
return MobileLayout();
}
},
),
),
),
);
}
class DesktopLayout extends StatelessWidget {
const DesktopLayout({super.key});
@override
Widget build(BuildContext context) {
return Container(
color: Colors.pink,
child: Align(
alignment: Alignment.center,
child: Text('Desktop Layout'),
),
);
}
}
class MobileLayout extends StatelessWidget {
const MobileLayout({super.key});
@override
Widget build(BuildContext context) {
return Container(
color: Colors.purple,
child: Align(
alignment: Alignment.center,
child: Text('Mobile Layout'),
),
);
}
}- OrientationBuilder is a widget that allows you to create layouts that adjust to changes in orientation, such as rotating the device from portrait to landscape mode.
void main() {
runApp(
MaterialApp(
home: Scaffold(
body: OrientationBuilder(
builder: (BuildContext context, Orientation orientation) {
if (orientation == Orientation.portrait) {
return PortraitLayout();
} else {
return LandscapeLayout();
}
},
),
),
),
);
}
class LandscapeLayout extends StatelessWidget {
const LandscapeLayout({super.key});
@override
Widget build(BuildContext context) {
return Container(
color: Colors.green,
child: Align(
alignment: Alignment.center,
child: Text('Landscape Layout'),
),
);
}
}
class PortraitLayout extends StatelessWidget {
const PortraitLayout({super.key});
@override
Widget build(BuildContext context) {
return Container(
color: Colors.yellow,
child: Align(
alignment: Alignment.center,
child: Text('Portrait Layout'),
),
);
}
}Conclusion
Building apps that feel native across phones, tablets, web, and desktop no longer means writing separate UIs for each platform. Flutter gives you three complementary tools to handle this: MediaQuery for reading device properties like screen size, orientation, and pixel density, LayoutBuilder for adapting your layout based on the available space within a widget’s constraints and OrientationBuilder for responding specifically to orientation changes.







