Irbisa · Cheatsheet
Responsive & Adaptive UI
Breakpoints, LayoutBuilder, NavigationRail, foldables, orientation, SafeArea, text scaling
What is the difference between responsive and adaptive UI in Flutter?
What is the difference between
MediaQueryandLayoutBuilder, and which one should decide a widget's layout?class ProductCard extends StatelessWidget { const ProductCard({super.key, required this.product}); final Product product; @override…What breakpoints do you use to switch a screen between phone, tablet and desktop layouts, and where does that check go?
enum WindowSize { compact, medium, expanded, large, extraLarge } WindowSize windowSizeOf(double width) => switch (width) { < 600 => WindowSize.compact, < 840 => WindowSize.medium, < 1200 => WindowSize.expanded,…How do you handle orientation in Flutter:
MediaQuery.orientationOf,OrientationBuilder, and locking withSystemChrome?Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); // a hint: large Android screens and iPad windows may ignore it await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); runApp(const App());}…A teammate scales every size by screen width with
flutter_screenutil. Why do many teams avoid that, and what do they use instead?// wrong: every size proportional to a 375 x 812 design frame// SizedBox(// width: 343.w, height: 48.h, // 763 x 71 dp on an 834 dp wide iPad// child: FilledButton(onPressed: pay, child: Text('Pay', style: TextStyle(fontSize: 16.sp))),// )…Opening the keyboard rebuilds widgets that have no text field anywhere near them. What does
MediaQuery.of(context)have to do with it?On an iPhone the 34 px gap under this
SendBarvanishes as soon as the keyboard opens. Which inset isSafeAreareading, and is this a bug?Scaffold( body: Column( children: [ const Expanded(child: MessageList()), SafeArea( top: false, child: SendBar(controller: _controller), ), ], ),);Find the bug: this sign-in screen looks fine until the keyboard opens, then a yellow-and-black overflow stripe appears at the bottom.
class SignInScreen extends StatelessWidget { const SignInScreen({super.key}); @override Widget build(BuildContext context) { final height = MediaQuery.sizeOf(context).height; return Scaffold( appBar: AppBar(title: const Text('Sign in')), body: Column(children: [ SizedBox(height: height * 0.4, child: const Center(child: FlutterLogo(size: 96))), const SignInForm(), // two fields and a button, about 200 px ]), ); }}Your custom bottom bar shows three dots for labels at 200% system font size. What does
MediaQuery.textScalerOfgive you, and where is clamping defensible?final scaler = MediaQuery.textScalerOf(context);final large = scaler.scale(14) > 20; // the real painted size, not 14 * factor // right: reflow instead of truncating at 200%final userRow = large ? Column(…This grid is 1000 px wide. How many columns does it lay out, how wide is each card, and what changes inside a 400 px side pane?
GridView.builder( gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 240, crossAxisSpacing: 16, mainAxisSpacing: 16, childAspectRatio: 0.75, ), itemCount: products.length, itemBuilder: (context, i) => ProductCard(product: products[i]),);A row of cards is wrapped in
IntrinsicHeightso they match the tallest one, and the list starts to jank. What is that widget costing you?Find the bug:
StatCardlays out correctly in a grid, but the dashboard's horizontal strip of the same cards throws a layout error.class StatCard extends StatelessWidget { const StatCard({super.key, required this.stat}); final Stat stat; @override Widget build(BuildContext context) => LayoutBuilder( builder: (context, c) => c.maxWidth >= 240 ? Row(children: [Expanded(child: Text(stat.label)), Text(stat.value)]) : Column(children: [Text(stat.label), Text(stat.value)]), );}// dashboard: a strip of cards that scrolls sidewaysWidget statStrip(List<Stat> stats) => ListView( scrollDirection: Axis.horizontal, children: [for (final s in stats) StatCard(stat: s)], );Resizing a desktop window from phone width to full screen resets the tab you were on. How do you wire a bottom bar and a navigation rail to one router?
const destinations = [ (icon: Icons.inbox, label: 'Inbox'), (icon: Icons.send, label: 'Sent'),]; // builder of StatefulShellRoute.indexedStack…The iOS build has to feel native. How much do Flutter's
.adaptiveconstructors give you, and what is left to branch by hand?// Controls: the adaptive constructor is the whole changeSwitch.adaptive(value: enabled, onChanged: onToggle); // CupertinoSwitch on Apple showAdaptiveDialog<void>( context: context, builder: (ctx) => AlertDialog.adaptive(…The same Flutter screen now ships to desktop and web. What has to change for a mouse and keyboard beyond a wider layout?
class ContactRow extends StatelessWidget { const ContactRow({super.key, required this.contact}); final Contact contact; @override Widget build(BuildContext context) {…Your inbox is a list on a phone and list-plus-message on a tablet. After rotating into two panes, the back gesture pops an invisible route. How do you model this?
class _InboxScreenState extends State<InboxScreen> { String? _selectedId; // lives ABOVE the breakpoint final _navKey = GlobalKey<NavigatorState>(); void _select(String? id) => setState(() => _selectedId = id);…A desktop user drags this window narrower: the list jumps back to the top and the selection is gone. What happens to the state when the width crosses 840?
class OrdersScreen extends StatelessWidget { const OrdersScreen({super.key}); @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, c) => c.maxWidth >= 840 ? const WideOrders() // StatefulWidget: list + detail, owns _selectedId : const NarrowOrders(), // StatefulWidget: list only, owns _selectedId ); }}The app is dragged into Android split screen, and on a foldable half the form ends up under the hinge. What does Flutter tell you about that hardware?
class HingeAwarePanes extends StatelessWidget { const HingeAwarePanes({super.key, required this.start, required this.end}); final Widget start; final Widget end; @override…The design has to hold at four widths and at 200% text scale. How do you test that without maintaining forty golden files?
const sizes = [Size(390, 844), Size(673, 841), Size(1024, 768), Size(1440, 900)]; void main() { for (final size in sizes) { for (final scale in const [1.0, 2.0]) { testWidgets('orders at ${size.width.toInt()} dp, ${scale}x text', (tester) async {…Phone, tablet and desktop from one Flutter codebase: do you write three widget trees or one tree full of width conditionals?