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.

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.

, , , and 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

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:

First, you check the orientation from MediaQuery . If it’s landscape, then you have a details page. Second, you declare a child widget to use later. Next, if you have a details page, you declare the child as a row of widgets. For this, the row contains the list of chats as a first item. Then, the next item in the row is the chat page showing the conversation. If you don’t have a details page, the child will be the list of chats. 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:

And this for 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:

First, you declare a LayoutBuilder as the child of SafeArea . 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. Next, if you have a details page, you declare child as a row of widgets. For this, the row contains the list of chats as a first item. Then, the next item in the row is the chat page showing the conversation. 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.

Auto-Resizing Text Based on Parent Widget Size

As you can see in the landscape screenshot above, the text in the colored boxes for the user isn’t resizing properly to fill the box. You can’t just increase the size of the font because you might go over the box. The right way to do it is to allow the widget to scale according to the size of the parent widget.

The Flutter widget FittedBox can solve this problem.

Using FittedBox

Open AvatarImageView.dart in lib/widgets and check the contents of _buildContent(Color textColor) . You can see here a Text widget that renders the initials of the user. The font size is 14. Surround the Text widget with a FittedBox like below:

// 1 return FittedBox( // 2 fit: BoxFit.contain, // 3 child: Text( initials, style: TextStyle(color: textColor, fontSize: 14), ), );

This makes the Text widget fill the parent widget and follows the BoxFit.contain rules. Going over each line:

First, you declare a FittedBox as a parent of the Text widget. Second, you use the BoxFit.contain fit to make it scale as big as it can without going out of the widget box. Finally, you declare the original Text widget as a child.

Build and run the project. You’ll see the following in landscape orientation:

Now you see that the text has resized accordingly. In general, you can use other types of BoxFit . You can see how each of them behave in the image below:

Providing Constraints to Children

There’s a paper clip image attachment button in your chat app. This should allow you to select images from a gallery. Usually, you want to display it within the chat view so you can still see the conversation while picking an image.

Right now, this is not displaying. Click on the attach button and you’ll only see the following for portrait:

and this for landscape:

Using AspectRatio

You need to fix the broken gallery. First, open the ConversationPage.dart file in the lib folder. Then look for the line with SquareGallery() with a TODO.

That widget isn’t appearing because it’s a child of Column and it doesn’t have enough information to determine its own height. So, wrap it in a AspectRatio widget to give it constraints. Replace the SquareGallery() line with the following:

AspectRatio( aspectRatio: 3, child: SquareGallery(), ),

Now that the gallery is wrapped in AspectRatio , it’ll have a constraint that tries to follow the provided ratio. It’ll be three times wider than it is high. In addition, AspectRatio will try to find constraints that fit what you provided as ratio as well as the parent constraints.

However, if it can’t find such a constraint, it’ll give you one that follows only the ratio you provided. In that case, the widget might overflow.

Build and run the project. When clicking the image attachment button, you should see a screen like this for portrait:

and this for landscape:

Congratulations! Now you’ve fixed the layout issues in the app. You can finally be responsive again in the chats! :]

CustomMultiChildLayout

In addition to the basic widgets for responsive design, Flutter also provides a way to layout widgets on your own with CustomMultiChildLayout . You’ll now see how to use it, but only in theory. Because CustomMultiChildLayout is such a big topic, you’ll only see the basics here.

Check out the code snippet below:

CustomMultiChildLayout( delegate: delegate, children: widgets, )

Here you declare a CustomMultiChildLayout with a custom delegate. The delegate can be an object of a class like the following:

// 1 class RWDelegate extends MultiChildLayoutDelegate { // 2 @override void performLayout(Size size) { // Do your layout here } // 3 @override bool shouldRelayout(MultiChildLayoutDelegate oldDelegate) => false; }

Here’s what you did:

First, you declare a subclass of MultiChildLayoutDelegate . Second, you override the performLayout method. Here you need to layout the children widgets using the layoutChild and positionChild methods. Finally, you return a boolean from shouldRelayout if the widget should perform a layout again. This method should decide based on your own widget’s parameters or state.

You can read the full docs for this widget here and for the delegate here. Look for a full tutorial on CustomMultiChildLayout on our Flutter page soon!

Where to Go From Here?

You can download the completed project by clicking on the Download Materials button at the top or bottom of the tutorial.

If you’re interested, check out the official docs for responsive apps in Flutter here and the official design cookbook here.

There’s also good guides on how to develop apps for various screen sizes and orientations here and here.

The Flutter team also made a presentation at Google I/O 2019 about adaptive UIs in Flutter. You can watch it here.

You also might want to read about the text accessibility settings on each platform.

I hope you enjoyed this tutorial on Responsive Design with Flutter! If you have any questions or comments, please join the forum discussion below.