Popular Searches
Popular Course Categories
Popular Courses

Creating consistent layouts across devices

Creating consistent layouts across devices

Flutter Responsive Design


Creating Consistent Layouts Across Devices in Flutter


Creating consistent layouts across devices means designing a Flutter application that maintains a clear visual structure, usable spacing, readable content, and predictable interactions across phones, tablets, foldables, desktops, and web browsers. Flutter's responsive and adaptive approach focuses on fitting the interface into the available space and making the interface usable within that space. :contentReference[oaicite:0]{index=0}




1. What Does Consistent Layout Mean?


A consistent layout does not mean that every device must display exactly the same UI. Instead, the application should preserve its important design language while adapting the arrangement of widgets to the available space.


For example, a shopping application can use the same product cards, colors, typography, and business logic on every device while changing the number of columns:



  • Mobile: one product per row.

  • Tablet: two or three products per row.

  • Desktop: several reasonably sized products per row.


This approach maintains consistency while still taking advantage of different screen sizes.




2. Responsive vs Adaptive Layout








ResponsiveAdaptive
Adjusts the placement and size of UI elements.Chooses an appropriate layout or interaction model.
Focuses on available space.Focuses on usability in that space.
Example: changing grid columns.Example: changing NavigationBar to NavigationRail.
Usually changes the arrangement.Can change the information architecture.

Flutter recommends considering both responsive and adaptive behavior when supporting different form factors. :contentReference[oaicite:1]{index=1}




3. Main Principle: Design for Available Space


A common mistake is assuming that a phone always has a small amount of space and a tablet always has a large amount of space. Modern applications can run in resizable windows, split-screen modes, foldable configurations, and desktop environments.


Therefore, layout decisions should generally be based on the available application window size instead of a device category. :contentReference[oaicite:2]{index=2}


Example


final width = MediaQuery.sizeOf(context).width;

if (width < 600) {
  return const MobileLayout();
}

return const LargeLayout();




4. Use MediaQuery.sizeOf for Window-Level Decisions


MediaQuery.sizeOf(context) provides the current application window size in logical pixels. It is useful when an entire screen needs to change according to the available window size. :contentReference[oaicite:3]{index=3}


Widget build(BuildContext context) {
  final size = MediaQuery.sizeOf(context);

  return Column(
    children: [
      Text('Width: ${size.width}'),
      Text('Height: ${size.height}'),
    ],
  );
}


Responsive Example


Widget build(BuildContext context) {
  final width = MediaQuery.sizeOf(context).width;

  return Scaffold(
    body: width < 600
        ? const MobileHomePage()
        : const DesktopHomePage(),
  );
}




5. Use LayoutBuilder for Component-Level Responsiveness


LayoutBuilder provides the constraints given to a widget by its parent. It is especially useful when a reusable component should respond to the space available to that component rather than the entire application window. :contentReference[oaicite:4]{index=4}


LayoutBuilder(
  builder: (context, constraints) {
    if (constraints.maxWidth < 400) {
      return const CompactCard();
    }

    return const WideCard();
  },
)


MediaQuery vs LayoutBuilder








MediaQuery.sizeOfLayoutBuilder
Measures the application window.Measures constraints from the parent.
Useful for page-level layouts.Useful for reusable components.
Returns a Size.Provides BoxConstraints.
Good for navigation changes.Good for card, panel, and component changes.



6. Establish a Consistent Design System


A design system helps different screens look like parts of the same application.


A Flutter design system can define:



  • Primary and secondary colors.

  • Typography.

  • Spacing values.

  • Border radius.

  • Button styles.

  • Card styles.

  • Input field styles.

  • Icon sizes.

  • Elevation.

  • Common responsive breakpoints.


Example Theme


MaterialApp(
  theme: ThemeData(
    useMaterial3: true,
    colorSchemeSeed: Colors.blue,
    scaffoldBackgroundColor: Colors.white,
    inputDecorationTheme: const InputDecorationTheme(
      border: OutlineInputBorder(),
    ),
  ),
  home: const HomePage(),
)

Using centralized theme values reduces unnecessary differences between screens.




7. Use Consistent Spacing


Spacing should follow a predictable system instead of using random values throughout the application.


Example Spacing System


class AppSpacing {
  static const double xs = 4;
  static const double sm = 8;
  static const double md = 16;
  static const double lg = 24;
  static const double xl = 32;
}

Usage


Padding(
  padding: const EdgeInsets.all(AppSpacing.md),
  child: Column(
    children: [
      const Text('Product'),
      SizedBox(height: AppSpacing.sm),
      const Text('Description'),
    ],
  ),
)



8. Use Consistent Content Widths


On large displays, allowing every component to stretch across the entire window can make content difficult to read and visually unbalanced. Flutter's large-screen guidance recommends avoiding layouts where text or boxes simply occupy the full width of large screens. :contentReference[oaicite:5]{index=5}


Example


Center(
  child: ConstrainedBox(
    constraints: const BoxConstraints(
      maxWidth: 1200,
    ),
    child: Padding(
      padding: const EdgeInsets.all(24),
      child: Column(
        children: const [
          Text('Application Content'),
        ],
      ),
    ),
  ),
)

This creates a maximum content area while still allowing the outer screen to grow.




9. Use Consistent Page Padding


Page content should maintain comfortable margins on different screen sizes.


final width = MediaQuery.sizeOf(context).width;

final horizontalPadding = width < 600
    ? 16.0
    : width < 1200
        ? 24.0
        : 40.0;

return Padding(
  padding: EdgeInsets.symmetric(
    horizontal: horizontalPadding,
  ),
  child: const PageContent(),
);


The goal is not to make every device use identical padding, but to preserve a consistent visual rhythm.




10. Create Meaningful Breakpoints


Breakpoints define when a layout should change. They should be based on where your content needs more or less space rather than arbitrary device names.


const mobileBreakpoint = 600.0;
const tabletBreakpoint = 900.0;
const desktopBreakpoint = 1200.0;

Responsive Layout Function


Widget responsiveLayout(BuildContext context) {
  final width = MediaQuery.sizeOf(context).width;

  if (width < mobileBreakpoint) {
    return const MobileLayout();
  }

  if (width < desktopBreakpoint) {
    return const TabletLayout();
  }

  return const DesktopLayout();
}


These breakpoint values are examples. Your application should select breakpoints according to the actual space required by its layout. Flutter's documentation specifically recommends branching based on available window size rather than device type. :contentReference[oaicite:6]{index=6}




11. Keep the Same Visual Identity Across Devices


Responsive layouts may change structure, but important visual elements should remain consistent.



  • Use the same color palette.

  • Use the same typography hierarchy.

  • Use consistent icon styles.

  • Use consistent button appearance.

  • Use consistent border radius.

  • Use consistent spacing rules.

  • Use the same terminology.

  • Use the same core interactions where appropriate.


Example


ElevatedButton(
  onPressed: () {},
  style: ElevatedButton.styleFrom(
    minimumSize: const Size(140, 48),
    shape: RoundedRectangleBorder(
      borderRadius: BorderRadius.circular(12),
    ),
  ),
  child: const Text('Continue'),
)



12. Use Flexible and Expanded


Hard-coded widths can cause overflow. Expanded and Flexible allow children to use available space more effectively.


Row(
  children: [
    Expanded(
      flex: 2,
      child: Container(
        height: 120,
        child: const Center(
          child: Text('Main Content'),
        ),
      ),
    ),
    const SizedBox(width: 16),
    Expanded(
      flex: 1,
      child: Container(
        height: 120,
        child: const Center(
          child: Text('Side Content'),
        ),
      ),
    ),
  ],
)



13. Change Row to Column When Space Is Limited


A common responsive pattern is displaying content side-by-side on large screens and vertically on smaller screens.


LayoutBuilder(
  builder: (context, constraints) {
    if (constraints.maxWidth < 700) {
      return const Column(
        children: [
          ProductImage(),
          ProductDetails(),
        ],
      );
    }

    return const Row(
      children: [
        Expanded(
          child: ProductImage(),
        ),
        SizedBox(width: 24),
        Expanded(
          child: ProductDetails(),
        ),
      ],
    );
  },
)




14. Maintain Consistent Card Design


Cards should use the same visual language even when their arrangement changes.


class ProductCard extends StatelessWidget {
  final String name;
  final String price;

  const ProductCard({
    super.key,
    required this.name,
    required this.price,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      elevation: 2,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(16),
      ),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Icon(Icons.shopping_bag, size: 40),
            const SizedBox(height: 12),
            Text(
              name,
              style: Theme.of(context).textTheme.titleMedium,
            ),
            const SizedBox(height: 8),
            Text(price),
          ],
        ),
      ),
    );
  }
}


The same reusable card can then be displayed in one-column, two-column, or multi-column layouts.




15. Use Responsive Grid Layouts


Instead of creating a different hard-coded grid for every device, let item size influence the number of columns.


GridView.builder(
  padding: const EdgeInsets.all(16),
  gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
    maxCrossAxisExtent: 280,
    crossAxisSpacing: 16,
    mainAxisSpacing: 16,
    childAspectRatio: 1.1,
  ),
  itemCount: products.length,
  itemBuilder: (context, index) {
    return ProductCard(
      name: products[index].name,
      price: products[index].price,
    );
  },
)

This allows the grid to adapt as the available width changes. Flutter's large-screen guidance recommends using reasonable item dimensions rather than simply stretching list content across a large display. :contentReference[oaicite:7]{index=7}




16. Keep Navigation Consistent but Adaptive


Navigation can use different widgets while preserving the same destinations.



  • Small windows can use NavigationBar.

  • Larger windows can use NavigationRail.

  • Very wide applications can use a sidebar.


Widget buildNavigation(BuildContext context) {
  final width = MediaQuery.sizeOf(context).width;

  if (width < 600) {
    return const NavigationBar(
      destinations: [
        NavigationDestination(
          icon: Icon(Icons.home),
          label: 'Home',
        ),
        NavigationDestination(
          icon: Icon(Icons.person),
          label: 'Profile',
        ),
      ],
    );
  }

  return const NavigationRail(
    destinations: [
      NavigationRailDestination(
        icon: Icon(Icons.home),
        label: Text('Home'),
      ),
      NavigationRailDestination(
        icon: Icon(Icons.person),
        label: Text('Profile'),
      ),
    ],
    selectedIndex: 0,
  );
}


Flutter's adaptive guidance describes sharing navigation destination data while changing the navigation presentation based on available space. :contentReference[oaicite:8]{index=8}




17. Share Data Between Different Layouts


Responsive layouts should not duplicate business data.


class Product {
  final String name;
  final double price;

  const Product({
    required this.name,
    required this.price,
  });
}


The same product list can be used by different layouts:


ResponsiveLayout(
  mobile: ProductList(products: products),
  tablet: ProductGrid(products: products),
  desktop: ProductGridWide(products: products),
)

Flutter's general adaptive approach recommends identifying common data and abstracting it so that multiple UI presentations can share the same information. :contentReference[oaicite:9]{index=9}




18. Create Reusable Responsive Layout Widgets


class ResponsiveLayout extends StatelessWidget {
  final Widget mobile;
  final Widget tablet;
  final Widget desktop;

  const ResponsiveLayout({
    super.key,
    required this.mobile,
    required this.tablet,
    required this.desktop,
  });

  @override
  Widget build(BuildContext context) {
    final width = MediaQuery.sizeOf(context).width;

    if (width < 600) {
      return mobile;
    }

    if (width < 1200) {
      return tablet;
    }

    return desktop;
  }
}


Usage


ResponsiveLayout(
  mobile: const MobileDashboard(),
  tablet: const TabletDashboard(),
  desktop: const DesktopDashboard(),
)

This reduces repeated breakpoint logic across multiple pages.




19. Use LayoutBuilder for Reusable Components


A reusable card may appear in different parts of the application. It should respond to the width actually provided by its parent.


class ResponsiveCard extends StatelessWidget {
  const ResponsiveCard({super.key});

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        final compact = constraints.maxWidth < 300;

        return Card(
          child: Padding(
            padding: EdgeInsets.all(compact ? 12 : 20),
            child: compact
                ? const Column(
                    children: [
                      Icon(Icons.star),
                      SizedBox(height: 8),
                      Text('Compact Card'),
                    ],
                  )
                : const Row(
                    children: [
                      Icon(Icons.star),
                      SizedBox(width: 12),
                      Text('Wide Card'),
                    ],
                  ),
          ),
        );
      },
    );
  }
}




20. Use SafeArea for Consistent Usable Space


SafeArea prevents important content from being obscured by notches, camera cutouts, rounded display edges, and operating-system UI. Flutter documentation recommends it as a useful starting point around the body of a Scaffold when appropriate. :contentReference[oaicite:10]{index=10}


Scaffold(
  body: SafeArea(
    child: Column(
      children: [
        const AppHeader(),
        Expanded(
          child: ListView(
            children: const [
              Text('Content'),
            ],
          ),
        ),
      ],
    ),
  ),
)



21. Maintain Consistent Typography


Typography should have a clear hierarchy across all screen sizes.


Text(
  'Dashboard',
  style: Theme.of(context).textTheme.headlineMedium,
)

Text(
  'Monthly Revenue',
  style: Theme.of(context).textTheme.titleMedium,
)

Text(
  '₹85,000',
  style: Theme.of(context).textTheme.headlineSmall,
)


Instead of defining completely different font styles for every device, use a shared typography system and make only necessary responsive adjustments.




22. Handle Long Text Correctly


Text should be allowed to wrap rather than forcing a fixed width.


Expanded(
  child: Text(
    'This is a long product description that should wrap naturally when the available width becomes smaller.',
    softWrap: true,
  ),
)

For long titles inside a row, Expanded or Flexible can prevent overflow.




23. Use Wrap for Flexible Actions


Wrap is useful when buttons, chips, filters, or tags need to move onto another line.


Wrap(
  spacing: 8,
  runSpacing: 8,
  children: [
    FilterChip(
      label: const Text('Popular'),
      selected: true,
      onSelected: (_) {},
    ),
    FilterChip(
      label: const Text('New'),
      selected: false,
      onSelected: (_) {},
    ),
    FilterChip(
      label: const Text('Discount'),
      selected: false,
      onSelected: (_) {},
    ),
  ],
)



24. Keep Forms Consistent Across Devices


Forms can use the same fields and validation logic while changing their arrangement.


Mobile


Column(
  children: [
    TextField(
      decoration: const InputDecoration(
        labelText: 'First Name',
      ),
    ),
    const SizedBox(height: 16),
    TextField(
      decoration: const InputDecoration(
        labelText: 'Last Name',
      ),
    ),
  ],
)

Large Screen


Row(
  children: [
    Expanded(
      child: TextField(
        decoration: const InputDecoration(
          labelText: 'First Name',
        ),
      ),
    ),
    const SizedBox(width: 16),
    Expanded(
      child: TextField(
        decoration: const InputDecoration(
          labelText: 'Last Name',
        ),
      ),
    ),
  ],
)

The fields retain the same labels, validation, theme, and interaction while their layout changes.




25. Use Maximum Width for Forms


Center(
  child: ConstrainedBox(
    constraints: const BoxConstraints(
      maxWidth: 600,
    ),
    child: const LoginForm(),
  ),
)

This prevents a login or registration form from becoming unnecessarily wide on desktop screens.




26. Create Consistent Dashboard Layouts


A dashboard can maintain the same cards and data while changing their arrangement.


LayoutBuilder(
  builder: (context, constraints) {
    final width = constraints.maxWidth;

    final columns = width < 600
        ? 1
        : width < 1000
            ? 2
            : 4;

    return GridView.count(
      crossAxisCount: columns,
      crossAxisSpacing: 16,
      mainAxisSpacing: 16,
      children: const [
        StatCard(title: 'Users', value: '12,450'),
        StatCard(title: 'Orders', value: '3,240'),
        StatCard(title: 'Revenue', value: '₹8,45,000'),
        StatCard(title: 'Pending', value: '128'),
      ],
    );
  },
)




27. Use List-Detail Layouts on Large Screens


Large screens provide enough space to display a list and its details simultaneously.


Row(
  children: [
    SizedBox(
      width: 300,
      child: ContactList(),
    ),
    const VerticalDivider(width: 1),
    const Expanded(
      child: ContactDetails(),
    ),
  ],
)

On a narrow screen, the application can navigate from the list to the details page instead.


Flutter's adaptive layout tutorial demonstrates this type of large-screen sidebar/detail pattern using Row, a fixed-width sidebar, and Expanded for the detail area. :contentReference[oaicite:11]{index=11}




28. Avoid Fixed Device-Specific Layouts


Problem


if (isPhone) {
  return PhoneLayout();
}

if (isTablet) {
  return TabletLayout();
}


Better


final width = MediaQuery.sizeOf(context).width;

if (width < 600) {
  return const CompactLayout();
}

return const ExpandedLayout();


The same application can run inside a small window on a large device, so physical device type does not necessarily represent the available application space. :contentReference[oaicite:12]{index=12}




29. Avoid Orientation-Only Decisions


Portrait and landscape do not necessarily correspond to a particular layout size. A landscape phone can still be narrow, while a portrait desktop window can be wide.


Less Reliable


if (MediaQuery.orientationOf(context) == Orientation.landscape) {
  return const DesktopLayout();
}

Better


final width = MediaQuery.sizeOf(context).width;

if (width >= 900) {
  return const WideLayout();
}

return const CompactLayout();


Flutter recommends using available size through MediaQuery.sizeOf or LayoutBuilder rather than orientation alone for major layout decisions. :contentReference[oaicite:13]{index=13}




30. Do Not Stretch Everything on Large Screens


One of the most common large-screen problems is allowing every component to become extremely wide.


Problem


SizedBox(
  width: double.infinity,
  child: TextField(
    decoration: const InputDecoration(
      labelText: 'Email',
    ),
  ),
)

Better


Center(
  child: ConstrainedBox(
    constraints: const BoxConstraints(
      maxWidth: 500,
    ),
    child: TextField(
      decoration: const InputDecoration(
        labelText: 'Email',
      ),
    ),
  ),
)

Large screens generally benefit from constrained content widths and multi-column layouts rather than unlimited horizontal stretching. :contentReference[oaicite:14]{index=14}




31. Support Different Input Devices


Consistent layouts should also provide consistent interaction across touch, mouse, trackpad, and keyboard.



  • Use sufficiently large touch targets.

  • Provide hover feedback where useful.

  • Support keyboard focus.

  • Support keyboard navigation.

  • Provide shortcuts for frequent desktop actions where appropriate.

  • Do not rely exclusively on hover for essential functionality.


Flutter's responsive best-practice guidance recommends supporting mice, trackpads, keyboard shortcuts, and keyboard navigation on larger devices. :contentReference[oaicite:15]{index=15}




32. Preserve Application State


Changing window size, orientation, or device configuration should not unnecessarily reset the user's progress.


Important state may include:



  • Selected tab.

  • Selected product.

  • Form input.

  • Search query.

  • Filters.

  • Scroll position.

  • Expanded sections.

  • Current navigation destination.


PageStorageKey Example


ListView.builder(
  key: const PageStorageKey('product-list'),
  itemCount: products.length,
  itemBuilder: (context, index) {
    return ListTile(
      title: Text(products[index].name),
    );
  },
)

Flutter recommends preserving application state when the window changes size, rotates, or folds/unfolds. PageStorageKey can help preserve list scroll state in appropriate cases. :contentReference[oaicite:16]{index=16}




33. Break Large Widgets into Smaller Widgets


Large responsive screens can become difficult to maintain. Break the UI into smaller reusable components.


class DashboardPage extends StatelessWidget {
  const DashboardPage({super.key});

  @override
  Widget build(BuildContext context) {
    return const Column(
      children: [
        DashboardHeader(),
        DashboardStats(),
        DashboardChart(),
        RecentOrders(),
      ],
    );
  }
}


Smaller widgets make responsive changes easier to implement and improve readability. Flutter also notes that smaller const widgets can provide performance benefits by allowing Flutter to reuse widget instances. :contentReference[oaicite:17]{index=17}




34. Use const Widgets Where Possible


const Padding(
  padding: EdgeInsets.all(16),
  child: Text('Welcome'),
)

Using const wherever appropriate makes widget trees easier to optimize and can reduce unnecessary work during rebuilds.




35. Maintain Consistent Image Ratios


Images should maintain predictable proportions across different screen sizes.


AspectRatio(
  aspectRatio: 16 / 9,
  child: Image.network(
    imageUrl,
    fit: BoxFit.cover,
  ),
)

Using AspectRatio prevents an image from becoming unexpectedly stretched when its container changes size.




36. Use Safe Scrolling


Small screens often have less vertical space. Make content scrollable rather than allowing it to overflow.


SafeArea(
  child: SingleChildScrollView(
    padding: const EdgeInsets.all(16),
    child: Column(
      children: [
        const Text('Registration'),
        const SizedBox(height: 24),
        const TextField(),
        const SizedBox(height: 16),
        const TextField(),
        const SizedBox(height: 24),
        ElevatedButton(
          onPressed: () {},
          child: const Text('Register'),
        ),
      ],
    ),
  ),
)



37. Do Not Lock Orientation Unnecessarily


A responsive application should generally work with different window sizes and orientations. Flutter recommends avoiding unnecessary orientation locks because modern applications may run in multi-window and foldable environments. :contentReference[oaicite:18]{index=18}


Instead, make the UI flexible enough to respond when the available space changes.




38. Consider Foldables and Multi-Window Layouts


Modern applications may be displayed in configurations where the application does not occupy the entire physical display. Flutter's adaptive APIs also expose information about display features such as folds and hinges through MediaQuery. :contentReference[oaicite:19]{index=19}


Responsive design should therefore avoid assumptions such as:


Device Type = Tablet
Full Screen = true
Orientation = Landscape
Available Width = Entire Display

Instead, base layout decisions on the space and constraints actually available to the application.




39. Consistent Platform Behavior


Consistency also includes respecting platform conventions. Android, iOS, Windows, macOS, Linux, and web applications can have different interaction expectations.


Flutter provides platform-specific behaviors for some interactions, while developers should consider platform idioms when designing application behavior. :contentReference[oaicite:20]{index=20}



  • Consider expected navigation behavior.

  • Consider keyboard and mouse conventions on desktop.

  • Consider touch interaction on mobile.

  • Use platform-appropriate interaction patterns when they improve usability.

  • Keep branding and core application identity consistent.




40. Create a Reusable App Container


A common container can keep content width, padding, and alignment consistent across pages.


class AppContainer extends StatelessWidget {
  final Widget child;

  const AppContainer({
    super.key,
    required this.child,
  });

  @override
  Widget build(BuildContext context) {
    return Center(
      child: ConstrainedBox(
        constraints: const BoxConstraints(
          maxWidth: 1200,
        ),
        child: Padding(
          padding: const EdgeInsets.symmetric(
            horizontal: 24,
            vertical: 20,
          ),
          child: child,
        ),
      ),
    );
  }
}


Usage


Scaffold(
  body: AppContainer(
    child: const DashboardContent(),
  ),
)



41. Complete Responsive Product Layout


class ProductPage extends StatelessWidget {
  const ProductPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Products'),
      ),
      body: SafeArea(
        child: LayoutBuilder(
          builder: (context, constraints) {
            if (constraints.maxWidth < 600) {
              return const ProductMobileLayout();
            }

            return const ProductLargeLayout();
          },
        ),
      ),
    );
  }
}

class ProductMobileLayout extends StatelessWidget {
  const ProductMobileLayout({super.key});

  @override
  Widget build(BuildContext context) {
    return ListView(
      padding: const EdgeInsets.all(16),
      children: const [
        ProductCard(
          name: 'Laptop',
          price: '₹65,000',
        ),
        ProductCard(
          name: 'Phone',
          price: '₹35,000',
        ),
        ProductCard(
          name: 'Tablet',
          price: '₹28,000',
        ),
      ],
    );
  }
}

class ProductLargeLayout extends StatelessWidget {
  const ProductLargeLayout({super.key});

  @override
  Widget build(BuildContext context) {
    return GridView.builder(
      padding: const EdgeInsets.all(24),
      gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
        maxCrossAxisExtent: 300,
        crossAxisSpacing: 20,
        mainAxisSpacing: 20,
      ),
      itemCount: 3,
      itemBuilder: (context, index) {
        const products = [
          ('Laptop', '₹65,000'),
          ('Phone', '₹35,000'),
          ('Tablet', '₹28,000'),
        ];

        return ProductCard(
          name: products[index].$1,
          price: products[index].$2,
        );
      },
    );
  }
}

class ProductCard extends StatelessWidget {
  final String name;
  final String price;

  const ProductCard({
    super.key,
    required this.name,
    required this.price,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Icon(
              Icons.shopping_bag,
              size: 48,
            ),
            const SizedBox(height: 12),
            Text(
              name,
              style: Theme.of(context).textTheme.titleLarge,
            ),
            const SizedBox(height: 8),
            Text(price),
          ],
        ),
      ),
    );
  }
}




42. Recommended Project Structure


lib/
├── main.dart
├── core/
│   ├── theme/
│   │   └── app_theme.dart
│   ├── constants/
│   │   └── app_spacing.dart
│   └── responsive/
│       └── responsive_layout.dart
├── models/
│   └── product.dart
├── widgets/
│   ├── app_container.dart
│   ├── product_card.dart
│   └── responsive_grid.dart
└── screens/
    ├── home/
    │   ├── mobile_home.dart
    │   ├── tablet_home.dart
    │   └── desktop_home.dart
    └── products/
        ├── mobile_products.dart
        └── desktop_products.dart

This structure separates reusable components, responsive utilities, models, themes, and screens.




43. Testing Consistent Layouts


Responsive consistency should be tested continuously rather than only at the end of development.


Test Different Widths



  • 320px approximately.

  • 375px approximately.

  • 430px approximately.

  • 600px.

  • 768px.

  • 900px.

  • 1024px.

  • 1200px.

  • 1440px and wider.


Also Test



  • Portrait mode.

  • Landscape mode.

  • Resized desktop windows.

  • Split-screen environments.

  • Different text scaling settings.

  • Keyboard navigation.

  • Mouse and trackpad interaction.

  • Touch interaction.

  • Long text.

  • Empty states.

  • Loading states.

  • Error states.




44. Common Mistakes


Mistake 1: Designing Only for One Phone


A layout that works on one phone width may fail on another.


Mistake 2: Using Device Names as Breakpoints


Use available window size instead of assuming phone, tablet, or desktop.


Mistake 3: Using Orientation as the Main Layout Rule


Orientation does not tell you the exact amount of available space.


Mistake 4: Using Excessive Fixed Widths


Fixed widths can create overflow and unusable layouts.


Mistake 5: Stretching Content Across the Entire Desktop


Use maximum content widths and multi-column structures where appropriate.


Mistake 6: Duplicating Business Logic


Different layouts should share the same data and business logic.


Mistake 7: Ignoring State Preservation


Users should not lose important work when the layout changes.


Mistake 8: Creating One Huge Widget


Break complex screens into reusable widgets.




45. Best Practices Checklist



  • Design for available space instead of device names.

  • Use MediaQuery.sizeOf for window-level decisions.

  • Use LayoutBuilder for local component constraints.

  • Use meaningful breakpoints.

  • Keep colors and typography consistent.

  • Use a centralized theme.

  • Use a consistent spacing system.

  • Use maximum content widths on large screens.

  • Use Expanded and Flexible for flexible layouts.

  • Use Wrap for flexible collections of actions.

  • Use responsive grids.

  • Adapt navigation without duplicating navigation data.

  • Use SafeArea where content needs protection.

  • Support touch, mouse, trackpad, and keyboard input.

  • Preserve application state.

  • Break large widgets into smaller widgets.

  • Use const widgets wherever appropriate.

  • Avoid unnecessary orientation locks.

  • Test different window sizes and form factors.




46. Practical Development Workflow



  1. Define the application's visual design system.

  2. Create reusable typography, colors, spacing, and component styles.

  3. Build the basic mobile layout.

  4. Identify where the layout becomes crowded.

  5. Create breakpoints based on actual content requirements.

  6. Use MediaQuery.sizeOf or LayoutBuilder.

  7. Change rows, columns, grids, and navigation when necessary.

  8. Apply maximum widths to large-screen content.

  9. Keep business logic and data shared.

  10. Preserve application state.

  11. Test multiple screen sizes.

  12. Test touch, mouse, keyboard, and accessibility scenarios.

  13. Refactor repeated responsive patterns into reusable widgets.




47. Quick Revision Table















ConceptPurpose
MediaQuery.sizeOfGets application window size.
LayoutBuilderGets constraints available to a widget.
BreakpointsDetermine when a layout should change.
ConstrainedBoxLimits maximum content size.
ExpandedShares available Row or Column space.
FlexibleAllows flexible sizing inside Flex layouts.
WrapMoves children to additional lines when necessary.
GridViewCreates responsive two-dimensional layouts.
SafeAreaProtects content from system UI and display cutouts.
ThemeDataCentralizes visual styling.
PageStorageKeyHelps preserve scroll state in suitable list scenarios.



48. Official Flutter Resources





49. Flutter Training Resources





50. Key Takeaways



  • Consistent layouts preserve the application's visual identity while allowing the structure to adapt.

  • Responsive design should be based on available application space.

  • Use MediaQuery.sizeOf for window-level measurements.

  • Use LayoutBuilder for component-level responsive behavior.

  • Do not rely on phone, tablet, or desktop labels for layout decisions.

  • Do not use orientation alone to determine the main application layout.

  • Use reusable components to maintain consistency.

  • Use centralized themes and spacing systems.

  • Use maximum widths to prevent large-screen content from becoming excessively wide.

  • Use responsive grids, flexible layouts, and adaptive navigation.

  • Preserve important application state when the window or layout changes.

  • Support different input devices and accessibility requirements.

  • Test the application across multiple screen sizes and platforms.


Creating consistent layouts in Flutter is therefore about combining a shared design system with responsive and adaptive layout techniques. The same application can maintain its identity while changing its structure to fit the space and interaction needs of each environment.


whatsapp