Popular Searches
Popular Course Categories
Popular Courses

Responsive UI in Flutter

Responsive UI in Flutter

Flutter Responsive Design

Responsive UI in Flutter

Responsive UI in Flutter means designing an application so that its interface automatically adjusts to the available space. A responsive application should remain usable and visually organized across mobile phones, tablets, foldables, desktops, and web browsers. Flutter describes responsive design as fitting the UI into the available space, while adaptive design focuses on making that UI usable within the available space. In real applications, both approaches are commonly used together. :contentReference[oaicite:0]{index=0}


1. What Is Responsive UI?

A responsive UI changes the arrangement, sizing, spacing, and positioning of widgets according to the available screen or window size.

For example, a product page may display:

  • One product column on a small mobile screen.
  • Two or three columns on a tablet.
  • Several appropriately sized columns on a desktop.

The important point is that responsive design does not mean creating completely separate applications for every device. Instead, the application uses the same data and reusable widgets while adapting their presentation.


2. Why Responsive UI Is Important

  • Flutter applications can run across many screen sizes and platforms.
  • Users may resize desktop or web application windows.
  • Tablets can run applications in multi-window mode.
  • Foldable devices can provide changing amounts of usable space.
  • Different devices support touch, mouse, trackpad, and keyboard input.
  • Large screens require different information arrangements from small screens.
  • Responsive layouts reduce overflow and improve usability.

Flutter's adaptive documentation emphasizes supporting different screen sizes, orientations, platforms, and input methods from a single codebase. :contentReference[oaicite:1]{index=1}


3. Responsive vs Adaptive UI

Responsive UI Adaptive UI
Fits UI elements into available space. Chooses an appropriate UI structure for the available space.
Changes sizes, spacing, columns, and positions. Can change navigation and interaction patterns.
Example: changing a grid from 4 columns to 2 columns. Example: changing NavigationBar to NavigationRail.
Primarily concerns layout. Primarily concerns usability and presentation.

4. Responsive Design Based on Available Space

A common mistake is assuming that a particular physical device always provides a particular amount of space. A tablet can run an application in a narrow split-screen window, while a desktop application can be resized to a small window.

For this reason, Flutter recommends making layout decisions based on the application's available window size rather than simply checking whether the device is a phone, tablet, or desktop. :contentReference[oaicite:2]{index=2}

Example

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

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

return const LargeLayout();

5. MediaQuery in Responsive UI

MediaQuery provides information about the current application environment. For responsive layouts, MediaQuery.sizeOf(context) is particularly useful for obtaining the current application window size.

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

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

MediaQuery.sizeOf returns the application's window size in logical pixels. Flutter recommends it when a layout decision depends on the overall application window. :contentReference[oaicite:3]{index=3}


6. Why Use MediaQuery.sizeOf Instead of MediaQuery.of?

Older Flutter code commonly uses MediaQuery.of(context).size. Modern Flutter guidance recommends specialized MediaQuery accessors such as MediaQuery.sizeOf(context) when only the size is needed.

final size = MediaQuery.sizeOf(context);

This more specifically expresses that the widget depends on the MediaQuery size and can avoid unnecessary rebuild dependencies on unrelated MediaQuery properties. :contentReference[oaicite:4]{index=4}


7. LayoutBuilder for Responsive Components

LayoutBuilder is useful when a widget should respond to the space provided by its parent rather than the entire application window.

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

    return const WideCard();
  },
)

LayoutBuilder provides a BoxConstraints object containing the minimum and maximum width and height available to the widget. :contentReference[oaicite:5]{index=5}


8. MediaQuery vs LayoutBuilder

MediaQuery.sizeOf LayoutBuilder
Measures the application window. Measures constraints from the parent.
Useful for page-level responsive decisions. Useful for component-level responsive decisions.
Returns a Size. Provides BoxConstraints.
Useful for navigation changes. Useful for cards, panels, and reusable widgets.

Flutter's general adaptive approach recommends using MediaQuery.sizeOf for whole-window measurements and LayoutBuilder for local layout constraints. :contentReference[oaicite:6]{index=6}


9. Responsive Breakpoints

A breakpoint is a width at which the application changes its layout because the current structure no longer fits comfortably.

const double mobileBreakpoint = 600;
const double tabletBreakpoint = 900;
const double desktopBreakpoint = 1200;

These values are examples. Breakpoints should be selected based on the requirements of your layout, not simply because a device has been classified as a phone or tablet.

Responsive Breakpoint Example

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

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

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

  return const DesktopLayout();
}

10. Flutter's Abstract, Measure, and Branch Approach

Flutter's general approach to adaptive applications can be summarized into three steps:

  1. Abstract: Identify data and widgets that can be shared between layouts.
  2. Measure: Determine how much space is available using MediaQuery or LayoutBuilder.
  3. Branch: Select the appropriate layout based on the available space.

This approach makes responsive code easier to maintain because the application does not duplicate business logic for every screen size. :contentReference[oaicite:7]{index=7}


11. Responsive Row and Column

A common responsive technique is changing a horizontal layout into a vertical layout when space becomes limited.

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(),
        ),
      ],
    );
  },
)

12. Using Expanded

Expanded allows a widget to occupy available space inside a Row, Column, or another Flex layout.

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

The flex values control how the available space is distributed.


13. Using Flexible

Flexible allows a child to use available space without requiring it to fill all remaining space.

Row(
  children: [
    Flexible(
      child: Text(
        'This text can shrink or wrap when the available width becomes smaller.',
      ),
    ),
    const Icon(Icons.info),
  ],
)

This is particularly useful for text-heavy rows where a fixed width could otherwise cause overflow.


14. Responsive Navigation

Navigation is an important part of responsive UI. A small window may work well with bottom navigation, while a wider window can use a navigation rail or 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.search),
          label: 'Search',
        ),
        NavigationDestination(
          icon: Icon(Icons.person),
          label: 'Profile',
        ),
      ],
    );
  }

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

Flutter's adaptive guidance uses navigation as an example of how the same destinations can be presented differently depending on available space. :contentReference[oaicite:8]{index=8}


15. Responsive Sidebar

Large screens can provide enough horizontal space for a persistent sidebar.

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

    return Row(
      children: [
        const SizedBox(
          width: 250,
          child: Sidebar(),
        ),
        const VerticalDivider(width: 1),
        const Expanded(
          child: DetailsPage(),
        ),
      ],
    );
  },
)

Flutter's adaptive layout tutorial demonstrates this general pattern, using a sidebar and details area on wider screens and navigation-based presentation on smaller screens. :contentReference[oaicite:9]{index=9}


16. Responsive GridView

Grid layouts should adapt to the available width instead of relying on a fixed number of columns for a specific physical device.

GridView.builder(
  gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
    maxCrossAxisExtent: 280,
    crossAxisSpacing: 16,
    mainAxisSpacing: 16,
    childAspectRatio: 1.2,
  ),
  itemCount: products.length,
  itemBuilder: (context, index) {
    return ProductCard(
      product: products[index],
    );
  },
)

Using maxCrossAxisExtent allows the number of columns to respond naturally to the available window width. Flutter's large-screen guidance recommends basing grid behavior on available window size rather than physical device categories. :contentReference[oaicite:10]{index=10}


17. Responsive Grid Using LayoutBuilder

LayoutBuilder(
  builder: (context, constraints) {
    int columns;

    if (constraints.maxWidth < 600) {
      columns = 1;
    } else if (constraints.maxWidth < 1000) {
      columns = 2;
    } else {
      columns = 4;
    }

    return GridView.count(
      crossAxisCount: columns,
      crossAxisSpacing: 16,
      mainAxisSpacing: 16,
      children: const [
        ProductCard(),
        ProductCard(),
        ProductCard(),
        ProductCard(),
      ],
    );
  },
)

18. Responsive Forms

Forms should remain readable and easy to use on both small and large screens.

Mobile Layout

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

Large Layout

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

19. Limit Form Width on Large Screens

Forms that stretch across an entire desktop window can become uncomfortable to use. A maximum width keeps the content focused.

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

Flutter recommends avoiding layouts that simply consume all available horizontal space on large displays. :contentReference[oaicite:11]{index=11}


20. ConstrainedBox for Responsive Content

ConstrainedBox can provide a maximum width for content while still allowing the application to expand around it.

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

21. Responsive Padding

Padding can be adjusted based on available space.

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

final padding = width < 600
    ? 16.0
    : width < 1000
        ? 24.0
        : 40.0;

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

Use a small and consistent spacing system rather than creating many arbitrary breakpoints.


22. Responsive Text

Text should be readable and should have enough room to wrap naturally.

Expanded(
  child: Text(
    'This is a responsive text example that can wrap when the available width becomes smaller.',
    softWrap: true,
  ),
)

Prefer theme-based typography rather than manually defining unrelated font sizes for every screen.

Using Theme Typography

Text(
  'Welcome to Flutter',
  style: Theme.of(context).textTheme.headlineMedium,
)

23. Responsive Cards

Create a reusable card and allow the parent layout to determine how many cards are displayed in a row.

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

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

  @override
  Widget build(BuildContext context) {
    return Card(
      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: 48,
            ),
            const SizedBox(height: 12),
            Text(
              name,
              style: Theme.of(context).textTheme.titleMedium,
            ),
            const SizedBox(height: 8),
            Text('₹$price'),
          ],
        ),
      ),
    );
  }
}

24. Responsive Images

Images should maintain their proportions when their container changes size.

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

AspectRatio helps maintain a predictable image shape across different layouts.


25. Responsive Wrap

Wrap is useful for buttons, filters, tags, and chips that need to move to another line when horizontal space becomes limited.

Wrap(
  spacing: 8,
  runSpacing: 8,
  children: [
    ElevatedButton(
      onPressed: () {},
      child: const Text('All'),
    ),
    ElevatedButton(
      onPressed: () {},
      child: const Text('Popular'),
    ),
    ElevatedButton(
      onPressed: () {},
      child: const Text('New'),
    ),
    ElevatedButton(
      onPressed: () {},
      child: const Text('Discount'),
    ),
  ],
)

26. Responsive Dashboard

A dashboard can use the same statistics and widgets 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,
      shrinkWrap: true,
      physics: const NeverScrollableScrollPhysics(),
      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. Responsive Login UI

LayoutBuilder(
  builder: (context, constraints) {
    final isWide = constraints.maxWidth >= 800;

    return Center(
      child: ConstrainedBox(
        constraints: const BoxConstraints(
          maxWidth: 900,
        ),
        child: Padding(
          padding: const EdgeInsets.all(24),
          child: isWide
              ? Row(
                  children: const [
                    Expanded(
                      child: LoginIllustration(),
                    ),
                    Expanded(
                      child: LoginForm(),
                    ),
                  ],
                )
              : const LoginForm(),
        ),
      ),
    );
  },
)

On a narrow screen, the form can occupy the available width. On a wider screen, the illustration and form can appear side by side.


28. Responsive List-Detail Layout

List-detail interfaces are useful for contacts, email, products, messages, and admin applications.

Large Screen

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

Small Screen

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (_) => const ContactDetails(),
  ),
);

Flutter's adaptive layout tutorial demonstrates the general concept of displaying related content side-by-side on larger screens and using navigation on smaller screens. :contentReference[oaicite:12]{index=12}


29. SafeArea in Responsive UI

SafeArea prevents important content from being obscured by display cutouts, notches, rounded corners, status bars, and other system UI.

Scaffold(
  body: SafeArea(
    child: Column(
      children: [
        const Text('Dashboard'),
        Expanded(
          child: ListView(
            children: const [
              ListTile(
                title: Text('Item 1'),
              ),
              ListTile(
                title: Text('Item 2'),
              ),
            ],
          ),
        ),
      ],
    ),
  ),
)

Flutter recommends using SafeArea around content that should avoid these intrusions; wrapping the body of a Scaffold is a common starting point. :contentReference[oaicite:13]{index=13}


30. Do Not Check Phone or Tablet Type

Bad Approach

if (isTablet) {
  return const TabletLayout();
}

return const PhoneLayout();

Better Approach

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

if (width >= 600) {
  return const LargeLayout();
}

return const SmallLayout();

Physical device type does not necessarily tell you how much space the application currently has. Flutter can run in resizable windows, multi-window environments, web browsers, and other configurations. :contentReference[oaicite:14]{index=14}


31. Avoid Orientation-Only Layout Decisions

Do not assume that landscape automatically means tablet or desktop.

Less Recommended

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

Recommended

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

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

return const CompactLayout();

Flutter specifically recommends using available size through MediaQuery.sizeOf or LayoutBuilder instead of relying on orientation alone for major layout decisions. :contentReference[oaicite:15]{index=15}


32. Avoid Unnecessary Fixed Widths

Problematic Code

Container(
  width: 700,
  child: const Text('Responsive content'),
)

This can overflow on smaller screens.

Better Code

Container(
  width: double.infinity,
  padding: const EdgeInsets.all(16),
  child: const Text('Responsive content'),
)

For large screens, combine flexible sizing with maximum-width constraints.


33. Use Maximum Width on Large Screens

Center(
  child: ConstrainedBox(
    constraints: const BoxConstraints(
      maxWidth: 1200,
    ),
    child: const DashboardContent(),
  ),
)

This prevents the interface from becoming excessively stretched on wide monitors. Flutter's large-screen recommendations specifically address avoiding full-width text fields and boxes. :contentReference[oaicite:16]{index=16}


34. Support Different Input Devices

Responsive UI is not only about screen size. Desktop and web users may interact with the application using a mouse, trackpad, or keyboard, while mobile users primarily use touch.

  • Provide comfortable touch targets.
  • Support mouse interaction.
  • Support keyboard navigation.
  • Provide hover feedback where appropriate.
  • Support trackpad scrolling.
  • Do not make essential functionality dependent only on hover.

Flutter recommends supporting mice, trackpads, keyboard shortcuts, and keyboard navigation, while Material widgets provide many default interaction behaviors. :contentReference[oaicite:17]{index=17}


35. Hover Interaction Example

class HoverCard extends StatefulWidget {
  const HoverCard({super.key});

  @override
  State createState() => _HoverCardState();
}

class _HoverCardState extends State {
  bool isHovered = false;

  @override
  Widget build(BuildContext context) {
    return MouseRegion(
      onEnter: (_) {
        setState(() {
          isHovered = true;
        });
      },
      onExit: (_) {
        setState(() {
          isHovered = false;
        });
      },
      child: AnimatedContainer(
        duration: const Duration(milliseconds: 200),
        padding: const EdgeInsets.all(20),
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(16),
          boxShadow: [
            BoxShadow(
              blurRadius: isHovered ? 12 : 4,
            ),
          ],
        ),
        child: const Text('Hover over me'),
      ),
    );
  }
}

36. Preserve Application State

A responsive application should not unnecessarily lose user data when its window changes size, rotates, or changes configuration.

Important state can include:

  • Form values.
  • Selected navigation item.
  • Search text.
  • Filters.
  • Selected products.
  • Scroll position.
  • Expanded sections.

Flutter recommends retaining or restoring application state as the device rotates, the window changes size, or a foldable changes configuration. :contentReference[oaicite:18]{index=18}


37. Preserve List Scroll Position

PageStorageKey can help preserve scroll state for lists that maintain the same general layout.

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

Flutter's adaptive best-practice guidance specifically recommends PageStorageKey for preserving suitable list state. :contentReference[oaicite:19]{index=19}


38. Do Not Lock Orientation Unnecessarily

Responsive applications should generally be able to handle different window sizes and shapes. Locking the application to one orientation can make adaptive behavior more difficult, particularly for large screens, multi-window environments, and foldables.

Flutter recommends avoiding unnecessary orientation locks and designing for different window sizes instead. :contentReference[oaicite:20]{index=20}


39. Responsive UI for Foldables

Foldable devices can change the available display area when they are folded or unfolded. Flutter's MediaQuery can also provide information related to display features such as folds or hinges.

final mediaQuery = MediaQuery.of(context);

final features = mediaQuery.displayFeatures;

For applications targeting foldables, avoid assuming that the application always occupies one fixed rectangular area. :contentReference[oaicite:21]{index=21}


40. Responsive UI for Desktop and Web

Desktop and web applications often provide significantly more horizontal space than mobile devices. However, simply stretching mobile content across the entire screen does not always create a good experience.

Useful desktop/web patterns include:

  • Maximum content widths.
  • Side navigation.
  • NavigationRail.
  • Multi-column dashboards.
  • List-detail layouts.
  • Responsive data tables.
  • Keyboard shortcuts.
  • Mouse hover feedback.

41. Responsive Data Table

Tables can become difficult to read on narrow screens. One solution is to switch from a table to cards or a list.

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

    return const DesktopOrderTable();
  },
)

This is an example of adaptive information architecture: the data remains the same while the presentation changes to suit the available space.


42. Break Large Widgets into Smaller Widgets

Complex responsive screens are easier to maintain when divided into smaller widgets.

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

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

Flutter recommends breaking complex widgets into smaller widgets. This can improve code organization and also make adaptive changes easier. Flutter notes that many small const widgets can also help rebuild performance. :contentReference[oaicite:22]{index=22}


43. Use const Widgets

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

Use const where widgets and their inputs are compile-time constants. This helps Flutter reuse constant widget instances and can reduce unnecessary work during rebuilds. :contentReference[oaicite:23]{index=23}


44. Separate Data from Responsive UI

Different screen layouts should share the same application data and business logic.

class Product {
  final String name;
  final double price;

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

The same product list can then be displayed using different layouts.

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

This prevents duplication and keeps responsive behavior focused on presentation.


45. Reusable ResponsiveLayout Widget

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(),
)

46. Responsive Component with LayoutBuilder

class AdaptiveCard extends StatelessWidget {
  const AdaptiveCard({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'),
                    ],
                  )
                : const Row(
                    children: [
                      Icon(Icons.star),
                      SizedBox(width: 12),
                      Text('Wide'),
                    ],
                  ),
          ),
        );
      },
    );
  }
}

47. Complete Responsive Home Screen Example

import 'package:flutter/material.dart';

void main() {
  runApp(const ResponsiveApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorSchemeSeed: Colors.blue,
        useMaterial3: true,
      ),
      home: const HomePage(),
    );
  }
}

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

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

            if (constraints.maxWidth < 1000) {
              return const TabletHome();
            }

            return const DesktopHome();
          },
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return ListView(
      padding: const EdgeInsets.all(16),
      children: const [
        DashboardCard(title: 'Users'),
        DashboardCard(title: 'Orders'),
        DashboardCard(title: 'Revenue'),
      ],
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return GridView.count(
      padding: const EdgeInsets.all(24),
      crossAxisCount: 2,
      crossAxisSpacing: 16,
      mainAxisSpacing: 16,
      children: const [
        DashboardCard(title: 'Users'),
        DashboardCard(title: 'Orders'),
        DashboardCard(title: 'Revenue'),
        DashboardCard(title: 'Products'),
      ],
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Center(
      child: ConstrainedBox(
        constraints: const BoxConstraints(
          maxWidth: 1200,
        ),
        child: Row(
          children: const [
            SizedBox(
              width: 240,
              child: Sidebar(),
            ),
            VerticalDivider(width: 1),
            Expanded(
              child: DesktopDashboard(),
            ),
          ],
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return ListView(
      children: const [
        ListTile(
          leading: Icon(Icons.dashboard),
          title: Text('Dashboard'),
        ),
        ListTile(
          leading: Icon(Icons.shopping_cart),
          title: Text('Orders'),
        ),
        ListTile(
          leading: Icon(Icons.people),
          title: Text('Users'),
        ),
      ],
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return GridView.count(
      padding: const EdgeInsets.all(24),
      crossAxisCount: 2,
      crossAxisSpacing: 20,
      mainAxisSpacing: 20,
      children: const [
        DashboardCard(title: 'Users'),
        DashboardCard(title: 'Orders'),
        DashboardCard(title: 'Revenue'),
        DashboardCard(title: 'Products'),
      ],
    );
  }
}

class DashboardCard extends StatelessWidget {
  final String title;

  const DashboardCard({
    super.key,
    required this.title,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Center(
        child: Text(
          title,
          style: Theme.of(context).textTheme.titleLarge,
        ),
      ),
    );
  }
}

48. Recommended Responsive Project Structure

lib/
├── main.dart
├── core/
│   ├── theme/
│   │   └── app_theme.dart
│   ├── constants/
│   │   └── app_constants.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/
        ├── product_list.dart
        └── product_grid.dart

49. Responsive UI Testing

Responsive design should be tested at different window sizes instead of testing only one device.

Test Different Widths

  • 320px approximately.
  • 375px approximately.
  • 430px approximately.
  • 600px.
  • 768px.
  • 900px.
  • 1024px.
  • 1200px.
  • 1440px and wider.

Also Test

  • Portrait orientation.
  • Landscape orientation.
  • Resizable desktop windows.
  • Browser resizing.
  • Split-screen configurations.
  • Foldable configurations where relevant.
  • Large text settings.
  • Keyboard navigation.
  • Mouse and trackpad interaction.
  • Touch interaction.
  • Long text.
  • Empty states.
  • Loading states.
  • Error states.

50. Common Responsive UI Mistakes

Mistake 1: Using Fixed Widths Everywhere

Large fixed widths can cause overflow on small screens.

Mistake 2: Checking Device Type

Do not use phone/tablet checks as the primary basis for layout decisions.

Mistake 3: Using Orientation as the Main Breakpoint

Orientation does not tell you exactly how much space your application has.

Mistake 4: Stretching Everything on Desktop

Use maximum widths and multi-column layouts rather than making every text field full width.

Mistake 5: Ignoring Keyboard and Mouse

Desktop and web users may need keyboard navigation, mouse interaction, and hover feedback.

Mistake 6: Losing User State

Changing the layout should not unnecessarily clear forms, selections, or scroll positions.

Mistake 7: Creating One Huge Widget

Large widgets make responsive changes harder to maintain.

Mistake 8: Testing Only One Screen

Always test different window sizes and configurations.


51. Responsive UI Best Practices Checklist

  • Design for available window space.
  • Use MediaQuery.sizeOf for application-window measurements.
  • Use LayoutBuilder for local component constraints.
  • Choose breakpoints based on layout requirements.
  • Avoid phone/tablet device checks for layout decisions.
  • Avoid orientation-only layout decisions.
  • Use Expanded and Flexible.
  • Use Wrap for flexible collections of controls.
  • Use responsive grids.
  • Use maximum content widths on large screens.
  • Use SafeArea where content needs protection.
  • Support touch, mouse, trackpad, and keyboard input.
  • Preserve important application and list state.
  • Break large screens into smaller widgets.
  • Use const widgets wherever appropriate.
  • Keep data and business logic independent from presentation.
  • Do not lock orientation unnecessarily.
  • Test multiple screen sizes and platforms.

52. Practical Responsive UI Development Workflow

  1. Identify the main content and user actions.
  2. Design the basic mobile-friendly layout.
  3. Identify where the layout becomes crowded.
  4. Create meaningful breakpoints.
  5. Use MediaQuery.sizeOf or LayoutBuilder.
  6. Adapt rows, columns, grids, and navigation.
  7. Use maximum widths for large-screen content.
  8. Keep business logic and data shared across layouts.
  9. Preserve application state.
  10. Add appropriate mouse and keyboard interactions.
  11. Test different window sizes.
  12. Test different input methods.
  13. Refactor repeated responsive logic into reusable widgets.

53. Quick Revision Table

Flutter Feature Purpose
MediaQuery.sizeOf Gets the current application window size.
LayoutBuilder Gets the constraints available to a widget.
Expanded Shares available space in Flex layouts.
Flexible Allows a child to flex without necessarily filling all available space.
Wrap Moves children to additional lines when necessary.
ConstrainedBox Limits content dimensions such as maximum width.
SafeArea Protects content from system UI and display cutouts.
GridView Creates responsive grid-based layouts.
NavigationBar Useful for compact navigation layouts.
NavigationRail Useful for wider layouts.
PageStorageKey Helps preserve list scroll state in suitable scenarios.
ThemeData Centralizes application styling.

54. Official Flutter Resources


55. Flutter Training Resources


56. Key Takeaways

  • Responsive UI allows Flutter applications to work effectively across different screen sizes.
  • Responsive design focuses on fitting UI into available space.
  • Adaptive design focuses on selecting an appropriate and usable interface for that space.
  • Use MediaQuery.sizeOf for application-window measurements.
  • Use LayoutBuilder for component-level responsive behavior.
  • Use breakpoints based on layout requirements rather than physical device names.
  • Use Expanded, Flexible, and Wrap to avoid unnecessary fixed dimensions.
  • Use responsive grids instead of hard-coded device-specific column counts.
  • Use maximum widths to prevent large-screen content from becoming excessively wide.
  • Use SafeArea to protect important content from system UI and display cutouts.
  • Support touch, mouse, trackpad, and keyboard interaction.
  • Preserve application state when the window or device configuration changes.
  • Break complex interfaces into smaller reusable widgets.
  • Test the application at different widths, orientations, platforms, and input methods.

Responsive UI in Flutter is ultimately about building flexible interfaces that respond to the space and capabilities available at runtime. By combining MediaQuery, LayoutBuilder, flexible widgets, meaningful breakpoints, adaptive navigation, reusable components, and careful testing, developers can create Flutter applications that remain usable across mobile, tablet, desktop, web, and other form factors. :contentReference[oaicite:24]{index=24}

Previous Next
whatsapp