Flutter being a cross-platform app development framework supports devices with hugely varying screen sizes, it can run on a device as small as a smartwatch to devices like a large TV. It's always a challenge to adapt your app to that variety of screen sizes and pixel densities using the same codebase.
There is no hard and fast rule for designing a responsive layout in Flutter. In this article, I will show you some of the approaches that you can follow while designing such a layout.
Before moving on to building responsive layouts in Flutter, I would like to throw some light on how Android and iOS handle layouts for different screen sizes natively.
In order to handle different screen sizes and pixel densities, following concepts are used in Android:
One of the revolutionary things introduced in the Android world for UI designing is the ConstraintLayout. It can be used for creating flexible and responsive UI designs that adapt to different screen sizes and dimensions. It allows you to specify the position and size for each view according to spatial relationships with other views in the layout.
For more information regarding
ConstraintLayout
, check out this article here.
But this does not solve the issue with large devices, where stretching or just resizing the UI components is not the most elegant way of taking advantage of the screen real-estate. This also applies in case of devices, like smartwatches which have very little screen real-estate, and resizing the components to fit that screen size might result in a weird UI.
To solve the above issue, you can use alternative layouts for different sized devices. For example, you can use split view in devices like tablets to provide a good user experience and use the large screen real-estate wisely.
In Android, you can define separate layout files for different screen sizes and the Android framework handles the switching between these layouts automatically as per the screen size of the device.
Using Fragment you can extract your UI logic into separate components, so that while designing multi-pane layouts for large screen sizes you do not have to define the logic separately. You can just reuse the logic that you have defined for each fragment.
Vector graphics create an image using XML to define paths and colors, instead of using pixel bitmaps. It can scale to any size without scaling artifacts. In Android, you can use VectorDrawable for any kind of illustration, such as icons.
The concepts used by iOS for defining responsive layouts are as follows:
Auto Layout can be used for constructing adaptive interfaces where you can define rules (known as constraints) that govern the content in your app. Auto Layout automatically readjusts layouts according to the specified constraints when certain environmental variations (known as traits) are detected.
Size classes are traits that are automatically assigned to content areas based on their size. iOS dynamically makes layout adjustments based on the size classes of a content area. On iPad, size classes also apply when your app runs in a multitasking configuration.
There are few other UI elements that you can use for building responsive UI on iOS, like UIStackView, UIViewController, UISplitViewController, etc.
Even if you are not an Android or iOS developer, by this point you should have received an idea how these platforms handle responsiveness natively.
In Android, for displaying multiple UI views on a single screen, you use Fragments which are like reusable components that can run inside an Activity of an app.
You can run multiple Fragments inside an Activity, but you cannot run multiple Activities in a single app at the same time.
In iOS, for controlling multiple view controllers, UISplitViewController
is used, which manages child view controllers in a hierarchical interface.
Now, coming to Flutter.
Flutter has introduced the concept of widgets. They are basically, the building blocks which can be connected together to build an entire app.
Remember in Flutter, every screen and the entire app is also a widget!
Widgets are reusable by nature, so you do not need to learn any other concept while building responsive layouts in Flutter.
As I said earlier, I will go over the important concepts which are required for developing responsive layouts, and then its your choice how you want to exactly implement it in your app.
You can use MediaQuery for retrieving the size (width/height) and orientation (portrait/landscape) of the screen.
An example of this is as follows:
https://gist.github.com/edeaeded71503ecab869754d0aa3cf07
Using the LayoutBuilder class you can get the BoxConstraints object, which can be used for determining the maxWidth and maxHeight of the widget.
REMEMBER: The main difference between
MediaQuery
&LayoutBuilder
is that MediaQuery uses the complete context of the screen rather than just the size of your particular widget. Whereas, LayoutBuilder can determine the maximum width and height of a particular widget.
An example demonstrating this is as follows:
https://gist.github.com/d0c9dd94d19c0c44a40d1c8b05e63e39
To determine a widget's current orientation you can use the OrientationBuilder class.
REMEMBER: This is different from the device orientation which you can retrieve using
MediaQuery
.
An example demonstrating this is as follows:
https://gist.github.com/e3eecaf609913e4a9f84a30493f5775f
The widgets that are especially useful inside a Column
or a Row
are Expanded
and Flexible
. Expanded widget expands a child of a Row, Column, or Flex so that the child fills the available space, whereas Flexible does not necessarily have to fill the entire available space.
An example showing various combinations of Expanded
and Flexible
is as follows:
https://gist.github.com/3b2e9558fb5cd105035d7cc77028a385
FractionallySizedBox widget helps to sizes its child to a fraction of the total available space. It is especially useful inside Expanded
or Flexible
widgets.
An example is as follows:
https://gist.github.com/5bf765aeaefec7d03e521c664ce01c3d
You can use AspectRatio widget to size the child to a specific aspect ratio. First of all, it tries the largest width permitted by the layout constraints and decides the height by applying the given aspect ratio to the width.
https://gist.github.com/04a2e7b76c7108bb758b02f8da6d4107
We have looked into most of the important concepts required for building a responsive layout in Flutter, except one.
Let's learn the last concept while building a sample responsive app.
Now, we will be applying some the concepts that I have described in the previous section. Along with it you will also learn another important concept for building layouts for large screens, that is split view.
We will be building a sample chatting app design called Flow.
The app will mainly consist of two major screens:
- HomePage (
PeopleView
,BookmarkView
,ContactView
) - ChatPage (
PeopleView
,ChatView
)
The main screen of the app after launch will be the HomePage
. It consists of two types to views:
- HomeViewSmall (consisting of
AppBar
,Drawer
,BottomNavigationBar
&DestinationView
) - HomeViewLarge (consisting of split view,
MenuWidget
&DestinationView
)
https://gist.github.com/d547b01dbe6c73aa452171df6d5bbd02
Here, LayoutBuilder
is for determining the maxWidth
and switch between the HomeViewSmall
and HomeViewLarge
widgets.
https://gist.github.com/9be148744f91ff35149c50b70a4e2fef
IndexedStack
with DestinationView
is used for switching between the views according to the selected item in the BottomNavigationBar
.
If you want to know more, check out the GitHub repository of this sample app present at the end of this article
https://gist.github.com/147965fba8cc1a99131c2b4f2e869543
For large screens, we will show a split view containing the MenuWidget
and the DestinationView
. You can see that it is really easy to create a split view in Flutter, you just have to place them side-by-side using a Row
and then in order to fill up the entire space just wrap both the views using Expanded
widget. You can also define the flex
property of the Expanded
widget, which will let you specify how much part of the screen each widget should cover (by default flex
is set to 1).
But, now if you move to a particular screen and then switch between the views, you will loose the context of the page, that is you will always return back to the first page that is Chats. To solve this here I have used multiple callback functions to return the selected page to the HomePage
. Practically, you should use a state management technique to handle this scenario. As the sole purpose of this article is to teach you building responsive layouts, I am not going into the complexities of any state management.
Modifying HomeViewSmall
:
https://gist.github.com/9804a3aa398d51b04f479cc99c7da49e
Modifying HomeViewLarge
:
https://gist.github.com/bf34764efa3ba8f86e254b9caebcac10
Modifying HomePage
:
https://gist.github.com/bbb3445a99fec29b10daa7c3c94fcce7
Now, your fully responsive HomePage
is complete.
This will be similar to the HomePage
, but here it will consist of the following two views:
- ChatViewSmall (consisting of
AppBar
,ChatList
&SendWidget
widget) - ChatViewLarge (consisting of
PeopleView
,ChatList
&SendWidget
widget)
https://gist.github.com/98a2fc0bcc28386e574fec7b67a54929
Here, I have used OrientationBuilder
along with the LayoutBuilder
, to vary the breakpointWidth
according to the orientation as I do not want to display the PeopleView
in small screen mobile while it is in landscape mode.
https://gist.github.com/3a229dac3f9b684c4033171236e7ad29
https://gist.github.com/2115914ee1657028deb28fbd1fa70641
We have successfully created a fully responsive app in Flutter. There are number of improvements you can still do in this app, one of them can be defining the fontSize to differ according to screen sizes.
While working with responsiveness, some of the amazing Flutter plugins that you can use are as follows: