In the world of Flutter development, “write once, run everywhere” is a promise that comes with a caveat: just because code runs on a tablet doesn’t mean it looks good on a tablet.
At our software house, ensuring our mobile app provided a seamless experience across a myriad of devices—from small Android phones to large iPad Pros—was a priority. We adopted a hybrid approach to responsive design that combines Proportional Scaling with Constraint-Based Layouts.
Here is a deep dive into the architecture we use to handle responsiveness in our Flutter codebase.
1. The Core Utility: Proportional Scaling
We use a SizeUtils class to establish a baseline. Most designs are created by UI/UX teams at a specific resolution (e.g., iPhone 11 Pro at 375×812). We treat this as our “Design Size.”
Our Sizer widget wraps the entire application, utilizing LayoutBuilder and OrientationBuilder to determine the current device’s effective width and height.
dart
// lib/core/utils/size_utils.dart
class Sizer extends StatelessWidget {
const Sizer({super.key, required this.builder});
final ResponsiveBuild builder;
@override Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, constraints) {
return OrientationBuilder(builder: (context, orientation) {
SizeUtils.setScreenSize(constraints, orientation);
return builder(context, orientation, SizeUtils.deviceType);
});
});
}
}
This allows us to use extension methods on standard numbers to scale UI
elements proportionally. Instead of hardcoding fontSize: 14, we write fontSize: 14.fSize. Whether on a tiny iPhone SE or a massive Samsung Ultra, the text remains readable and proportionally correct.
dart
extension ResponsiveExtension on num {
// Simple scaling logic based on design width (375)
double get h => (this * SizeUtils.width) / 375;
double get w => (this * SizeUtils.width) / 375;
double get fSize => (this * SizeUtils.width) / 375;
}
2. Handling Large Screens: Constraint-Based Layouts
However, proportional scaling isn’t enough. If you scale a login button proportionally on an iPad, it looks comically large. This is where Constraints come in.
For screens like our Login Page, we want the content to be centered and readable, not stretched edge-to-edge. We achieve this using a specific pattern: LayoutBuilder + SingleChildScrollView + ConstrainedBox.
The Login Screen Pattern
Here is how we implemented the Login Screen to look great on both phones and tablets:
dart
// features/auth/presentation/login_screen.dart
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
// Ensure the scroll view takes at least the full screen height
constraints: BoxConstraints(minHeight: constraints.maxHeight),
child: IntrinsicHeight(
child: Center(
// LIMIT WIDTH: This is the key for tablets/desktop!
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 500),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Form fields go here...
],
),
),
),
),
),
),
);
},
),
),
);
}
- Mobile: The screen width is usually < 500px, so the maxWidth: 500 constraint does nothing. The column fills the width (minus padding).
- Tablet/Web: The screen width is > 500px. The ConstrainedBox kicks in, capping the form width at 500px. The Center widget then places this 500px box perfectly in the middle of the screen.
3. Adaptive Orientation
Using the OrientationBuilder inside our Sizer, we can also trigger full UI changes. For dashboards, we might switch from a Column (vertical list) in Portrait mode to a Row (side-by-side view) in Landscape mode.
Conclusion
Responsive design in Flutter isn’t just about using MediaQuery. It’s about a strategy:
Scale proportionally for small variations (different phone sizes).
Constrain layout for large variations (tablets and desktops).
By combining SizeUtils for micro-adjustments and LayoutBuilder for macro-layout decisions, we ensure a consistent, professional look across every device our users use.

Top comments (0)