Responsive
Responsive Screen Guide in Flutter
Responsive design is an essential part of Flutter application development to ensure the app has a visually appealing interface across various screen sizes. This article will guide you on how to implement responsiveness using different methods in Flutter.
How to Adjust Screens with responsive_builder
and Mason for Different Screen Types
responsive_builder
and Mason for Different Screen TypesAdd the following to your
pubspec.yaml
:responsive_builder: ^latest_version
The responsive_builder
library helps divide the interface into different screen types such as mobile, tablet, and desktop. We can use ScreenTypeLayout.builder
and OrientationLayoutBuilder
to handle the interface based on screen type and orientation.
Below is how I build screens that can adapt to primary devices such as tablets and mobiles. My idea is to create small fragments of the UI and then assemble them according to the screen type that needs to be displayed. Here, we have 4 types: mobile portrait, mobile landscape, tablet portrait, and tablet landscape.
In a simple case, I will have a single common UI section for all screen types, called the body. Then, I arrange it into the four different screen types mentioned above.
import 'package:flutter/material.dart';
import 'package:responsive_builder/responsive_builder.dart';
part 'responsive_screen_mobile.dart';
part 'responsive_screen_tablet.dart';
part 'part_screen/responsive_screen_body.dart';
class ResponsiveScreen extends StatelessWidget {
const ResponsiveScreen({super.key});
@override
Widget build(BuildContext context) {
final body = const ResponsiveScreenBody();
return ScreenTypeLayout.builder(
mobile: (_) => OrientationLayoutBuilder(
portrait: (context) => ResponsiveScreenMobilePortrait(body: body),
landscape: (context) => ResponsiveScreenMobileLandscape(body: body),
),
tablet: (_) => OrientationLayoutBuilder(
portrait: (context) => ResponsiveScreenTabletPortrait(body: body),
landscape: (context) => ResponsiveScreenTabletLandscape(body: body),
),
);
}
}
Mobile Layouts
This is what will appear inside the mobile screen. You can arrange it freely according to the interface you have previously designed.
part of 'responsive_screen.dart';
class ResponsiveScreenMobilePortrait extends StatelessWidget {
const ResponsiveScreenMobilePortrait({super.key, required this.body});
final Widget body;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: body,
);
}
}
class ResponsiveScreenMobileLandscape extends StatelessWidget {
const ResponsiveScreenMobileLandscape({super.key, required this.body});
final Widget body;
@override
Widget build(BuildContext context) {
return Row(
children: [
const Spacer(flex: 130,),
Expanded(
flex: 1180,
child: body,),
const Spacer(flex: 130,),
],
);
}
}
Tablet Layouts
This is what will appear inside the tablet screen. You can freely arrange it based on the interface you have previously designed.
part of 'responsive_screen.dart';
class ResponsiveScreenTabletPortrait extends StatelessWidget {
const ResponsiveScreenTabletPortrait({super.key, required this.body});
final Widget body;
@override
Widget build(BuildContext context) {
return Row(
children: [
const Spacer(flex: 130),
Expanded(flex: 1180, child: body),
const Spacer(flex: 130),
],
);
}
}
class ResponsiveScreenTabletLandscape extends StatelessWidget {
const ResponsiveScreenTabletLandscape({super.key, required this.body});
final Widget body;
@override
Widget build(BuildContext context) {
return Row(
children: [
const Spacer(flex: 130),
Expanded(flex: 1180, child: body),
const Spacer(flex: 130),
],
);
}
}
Ensuring that mobile and tablet screens have different layouts enhances the user experience. However, one challenge is having to rewrite code every time a new screen is created. To save time, I use Mason to reduce my workload.
You can check out my dr_responsive
brick here: https://brickhub.dev/bricks/dr_responsive. If you're unfamiliar with Mason, refer to my article on Mason. I'm confident it will save you a lot of time.
Responsive Design Using Screen Ratios
In reality, phone screen sizes are not uniform even with the same aspect ratio. Use MediaQuery
to determine the screen ratio and adjust the UI accordingly.
My idea is to use this ratio combined with the proportions in Figma to ensure the UI doesn’t overflow or break and always maintains its proportions across different devices.
class ResponsiveWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
double screenWidth = MediaQuery.of(context).size.width;
double screenHeight = MediaQuery.of(context).size.height;
return Container(
width: screenWidth * 0.8, // Occupies 80% of the screen's width
height: screenHeight * 0.5, // Occupies 50% of the screen's height
color: Colors.blue,
child: const Center(child: Text("Responsive Container")),
);
}
}
You might find writing MediaQuery.of(context).size.width
quite long and time-consuming, so let me share my approach with you:
My idea is to create a variable that can be used throughout the entire project. For instance, I calculate the ratio by dividing the device's width by the design width in Figma to ensure compatibility across devices in landscape orientation.
This ratio will have a maximum value of 1 and a minimum value of 0.2, based on the design dimensions in Figma.
extension MyBuildContext on BuildContext {
double get myHeight => MediaQuery.of(this).size.height;
double get myWidth => MediaQuery.of(this).size.width;
/// Edit ratio of fontSize for responsive screen
double get dsgRatio {
final ratio = myWidth / 1920;
return ratio < 1
? ratio < 0.2
? 0.2
: ratio
: 1;
}
}
Then, it will be called as follows. For instance, let’s assume I have a SizedBox
ratio of 260px in Figma. Here’s how I would implement it in the code, adjusted with the global variable for screen ratio. Let me know if you'd like me to generate a full code example!
class ButtonAddCouponPartScreen extends StatelessWidget {
const ButtonAddCouponPartScreen({
super.key,
});
@override
Widget build(BuildContext context) {
final ratio = context.dsgRatio;
return SizedBox(
width: 260 * ratio,
);
}
}
Translation:
This method is often used for widgets when they require a width or height to display. If you'd like to learn more about responsive text sizing, check out my article on theming
Dark ModeUsing Expanded
and Spacer
Expanded
and Spacer
If you find the previous method resource-intensive as it involves determining the device width, calculating the ratio, and then arriving at the actual size of the widget, I have another solution for you:
Use Expanded
and Spacer
.
For example, the image below has the following design and size:
Now, I'll use Expanded
and Spacer
to create the interface, and the result will be similar to the one produced by the ratio-based method:

Expanded
and Spacer
You can clearly see the advantage here: there’s no need to calculate any ratios while creating the interface, yet it still produces a responsive result. I often use this method when designing interfaces for screens.
Conclusion
Above, I've introduced three methods for making a Flutter interface responsive. In practice, I use all three methods. Each has its own advantages and disadvantages, so make the most of this knowledge to make your application function more efficiently. If you want more examples of responsive screen design in my style, visit the website I built at https://wongcoupon.com/en/doc/help/flutter to experience it.
Last updated