Responsive Design for Flutter: Getting Started

In this Flutter Responsive Design tutorial you’ll learn how to build a Flutter app that responds to layout changes such as screen size and orientation. By JB Lorenzo.

Leave a rating/review
Download materials
Save for later
Share

Sometimes the configuration changes for an app on a mobile device. Maybe a keyboard suddenly appears, or the user rotates the device. Or perhaps you want to display your app on both small and large devices.

At any rate, your app needs to be responsive to these layout changes. If you use Responsive Design, it will.

In this Flutter tutorial, you’ll:

  • Build a chat app in Flutter that responds to layout changes.
  • Learn to use Flutter’s MediaQuery, LayoutBuilder, OrientationBuilder, FittedBox and AspectRatio widgets.
  • Learn to handle orientation changes.
  • Perform text resizing.
  • Constrain a child widget in a column.
  • Learn about the idea of the CustomMultiChildLayout widget.
Note: This tutorial assumes that you’re already familiar with the basics of Flutter development. If you’re new to Flutter, read through the Getting Started With Flutter tutorial. You should also have knowledge of using Android Studio with Flutter.

What is Responsive Design?

The concept of Responsive Design is all about using one set of code that respond to various changes to layout. Platforms such as the iOS and Android native SDKs tackled this issue with “universal layouts.” The universal layouts respond to layout changes by using constraints and automatically resizing elements.

There are a variety of reasons why layout needs to responsively change from initial designs.

Handling Different Device Types and Screen Sizes

Responsive Layouts

Your Flutter app can run on a phone, tablet, TV screen or (when they start supporting it) watch. Even within the category of phones there’s a large array of different resolutions and screen sizes. You need to make sure that the layout works as intended for each device type and screen size. Additionally, you can have different layouts for each device type and screen size.

With this in mind, Flutter provides several widgets and classes for responsive design. You’ll learn about some of these in this tutorial.

Handling Keyboard State Changes

Your interface might have text fields. The keyboard pops up when the user starts interacting with those fields. When that keyboard pops up, so do layout issues.

Android handles this with configuration changes for the keyboard. iOS uses internal notifications for keyboard state changes. But in Flutter, the Scaffold class automatically handles keyboard state changes.

In detail, Scaffold adjusts the bottom insets to make room for the keyboard. You can, however, disable this behavior by setting the resizeToAvoidBottomInset property to false.

You can read more about Scaffold‘s interaction with the keyboard here.

Handling Orientation Changes

Let’s face it, users can rotate their device, and will do so frequently. You could disable responding to this within your app, locking your app into portrait or landscape mode, but your app wouldn’t be as fun and might in fact be less useful with respect to user experience.

When rotation happens in Flutter, MediaQuery can help rebuild your layout. MaterialApp and WidgetsApp already use MediaQuery. If you use them, Flutter rebuilds your widgets under MaterialApp if orientation changes.

You can read more about MediaQuery here.

Now that you understand the reasons for Responsive Design, it’s time to see what Flutter widgets can do to help.

Getting Started

Download the starter project by clicking on the Download Materials button at the top or bottom of the tutorial. Then, open the starter project in Android Studio 3.4 or later. You can also use VS Code, but you’ll have to adapt instructions below as needed.

You should be using a recent version of Flutter, 1.5 or above. Be sure to get Flutter dependencies for the project if prompted to do so by Android Studio with a ‘Packages get’ has not been run message.

You’ll find the starter project provides some parts of the chat app you’ll be working with.

Using MediaQuery to Decide the Layout

Now you should try using MediaQuery to determine the layout. This is just one of the options you can use to respond to layout changes. You’ll get to use the other options in the next sections.

Go into the ChatListPage.dart file in the lib folder. Replace the contents of build(BuildContext context) with:

// 1
var hasDetailPage =
    MediaQuery.of(context).orientation == Orientation.landscape;
// 2
Widget child;

if (hasDetailPage) {
  // 3
  child = Row(
    children: [
      // 4
      SizedBox(
        width: 250,
        height: double.infinity,
        child: _buildList(context, hasDetailPage),
      ),
      // 5
      Expanded(child: _buildChat(context, selectedIndex)),
    ],
  );
} else {
  // 6
  child = _buildList(context, hasDetailPage);
}

return Scaffold(
  appBar: AppBar(
    title: Text("Chats"),
  ),
  body: SafeArea(
    // 7
    child: child,
  ),
);

With that, you layout the chat page using MediaQuery. Here’s what you did:

  1. First, you check the orientation from MediaQuery. If it’s landscape, then you have a details page.
  2. Second, you declare a child widget to use later.
  3. Next, if you have a details page, you declare the child as a row of widgets.
  4. For this, the row contains the list of chats as a first item.
  5. Then, the next item in the row is the chat page showing the conversation.
  6. If you don’t have a details page, the child will be the list of chats.
  7. Finally, you need to assign that child widget you created as a child of SafeArea.

Build and run the project — you should see a screen like this for portrait:

Chat list portrait

And this for landscape:

Chat list with detail landscape

You’ll notice the layout is different for portrait and landscape. You can also try running it on a different device like a tablet.

Using Widgets in Responsive Design

As mentioned previously, there are other widgets that achieve the same effect as MediaQuery. For example, LayoutBuilder allows you to do the same thing. You’ll see that in this section.

Aside from that, there are other layout problems with the chat app. For instance, the text size on the user avatar does not scale. You will fix that in a bit.

Using LayoutBuilder and OrientationBuilder

LayoutBuilder and OrientationBuilder are alternatives to MediaQuery for handling orientation changes. Time to see exactly how they work, starting with LayoutBuilder.

First, open ChatListPage.dart, like you did in the previous section. Replace the contents of build(...) with this:

return Scaffold(
  appBar: AppBar(
    title: Text("Chats"),
  ),
  body: SafeArea(
    // 1
    child: LayoutBuilder(builder: (builder, constraints) {
      // 2
      var hasDetailPage = constraints.maxWidth > 600;

      if (hasDetailPage) {
        // 3
        return Row(
          children: [
            // 4
            SizedBox(
              width: 250,
              height: double.infinity,
              child: _buildList(context, hasDetailPage),
            ),
            // 5
            Expanded(child: _buildChat(context, selectedIndex)),
          ],
        );
      } else {
        // 6
        return _buildList(context, hasDetailPage);
      }
    }),
  ),
);

With that, you layout the chat page again, this time using LayoutBuilder. Here’s what you did:

  1. First, you declare a LayoutBuilder as the child of SafeArea.
  2. Second, you determine if you have a details page using the maximum width of the parent widget. If it is greater than 600, then you have a details page.
  3. Next, if you have a details page, you declare child as a row of widgets.
  4. For this, the row contains the list of chats as a first item.
  5. Then, the next item in the row is the chat page showing the conversation.
  6. Finally, if you don’t have a details page, it’ll be the list of chats.

Build and run the project. You should see the same screen in the different orientations as in the previous section.

If you also want to try OrientationBuilder, replace the lines with LayoutBuilder and the setting of hasDetailPage with this:

child: OrientationBuilder(builder: (builder, orientation) {  
  var hasDetailPage = orientation == Orientation.landscape;

This minor change has the same effect. Instead of reading the width of the parent widget, you read the parent widget’s orientation from the builder. If the orientation is landscape, then you have a details page.

Build and run the project. You should see the same screens as before. As you can see, each of these various widgets can solve the problem of different orientations and screen sizes.

Next, you’ll fix the text that’s not resizing in the user’s avatar.