Debugging Layout Issues Using the Widget Inspector
In this article, you’ll see how layouts are constructed as well as learn about the Widget Inspector and how to use it to solve common layout errors in an app. By Ayush.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Debugging Layout Issues Using the Widget Inspector
20 mins
- Getting Started
- Using the Widget Inspector
- Understanding Constraint Flow
- Displaying Overflowing Content
- Making Fitting Content
- Preventing Infinite Viewports
- Preventing Horizontal Infinite Viewports
- Using UnconstrainedBox
- Using LimitedBox and FractionallySizedBox
- Implementing Responsiveness
- Where to Go From Here?
Preventing Infinite Viewports
Overflowing and fitting content are aesthetic requirements that help prevent visual inconsistencies. But some issues cause serious crashes when they occur.
Click the “Pop culture” card to open the list of movies with this planet as a location. You’ll see a white bottom sheet modal.
The IDE logs will also report several logs, starting with the main cause of the error at the top.
The vertical viewport in the message refers to the ListView
, which is a child of a Column
widget. This message becomes clearer when you recall that Column
gives its children unconstrained height. In this case, its child happens to be a ListView
, which extends vertically to infinity. Thus, the Flutter framework cannot render such a layout. If you could somehow provide a maximum height for the ListView
, the UI would render.
One option is to use a LimitedBox
, which is specifically used whenever the parent provides unconstrained dimensions for its children. It sets a maximum width or height for its child that doesn’t set its own dimensions in an unconstrained environment.
But this solution requires you to manually set a height for the ListView
, which in this case needs to occupy the remainder of the bottom sheet modal. The other, more generally accepted solution is to use an Expanded
widget. Expanded
, when used as a child of the Column
widget, occupies the maximum height that it can within its parent.
Open lib/widgets/movie_location_pageview.dart and replace the ListView
below //TODO add vertical Expanded
with the following code snippet:
Expanded(
child: ListView(
children: [
ListTile(
dense: true,
title: const Text('Cast'),
subtitle: Text(_item.cast),
),
ListTile(
dense: true,
title: const Text('Genre'),
subtitle: Text(_item.genre),
),
ListTile(
dense: true,
title: const Text('Director'),
subtitle: Text(_item.director),
),
ListTile(
title: Text(_item.synopsis),
),
],
),
),
Hot reload and note that the ListView
now occupies the whole remaining height in its parent.
Switch to the Layout Explorer and you can verify Expanded
‘s default behavior of occupying the available space in its parent (a Column
in this case).
Preventing Horizontal Infinite Viewports
The bottom sheet content has appeared. But another issue has appeared along with it. The movie title is displaying an overflow warning as well. Unlike Column
, Row
gives its children unconstrained width. Because the movie title exceeds the remaining width, an overflow occurs. This warning also appears in the widget tree of the Widget Inspector.
The lesson here is that infinite viewports are independent of direction and can occur anywhere.
The fix is to again wrap the Column
in an Expanded
widget to give it the available width of the Row
. Replace the Column
below //TODO add horizontal Expanded
with the following snippet:
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
'${_item.name}',
maxLines: 2,
style: const TextStyle(fontSize: 40),
),
Text(
'${_item.year}',
style: const TextStyle(fontSize: 20),
),
],
),
),
Hot reload, and you’ll finally see the list of movies containing the selected planet as a location. Neat!
Using UnconstrainedBox
Unlike the usecase for OverflowBox
, where it imposes its own constraints, sometimes you might need your content to have no constraints at all. This usecase commonly arises in widgets such as PageView
that force their children to occupy their entire space.
Open libs/solar_system.dart and look at the solar system implementation, using a PageView
. Both Layout Explorer and Details Tree in the Widget Inspector show a fixed width and height for the PageView
child as well.
Hence, the framework will render all the planets and the sun at the same size.
To correct this, you need to add a widget that provides its children the freedom to choose their size. Luckily, Flutter comes with the UnconstrainedBox
widget.
The UnconstrainedBox
does exactly what its name implies. Even though its parent may impose a constraint on it, it allows its child to be any size it wants.
Open lib/solar_system.dart and replace the return
below //TODO add UnconstrainedBox
with the following
return UnconstrainedBox(
child: GestureDetector(
child: body,
onTap: () {
context.explore(path);
},
),
);
Hot reload and see that Jupiter and every other heavenly body have been rendered at a proper size.
Using LimitedBox and FractionallySizedBox
But in some scenarios, using only an UnconstrainedBox
isn’t enough.
Click Jupiter and then the “Events” card.
You should see a horizontal PageView
listing various major events in the planet’s history. Go deeper into the Widget Inspector and look at the widget tree for the Card
. You’ll find that because Card
is a PageView
child, it’s forced to occupy the entire area. How about giving it some room to breathe? Wrap it in an UnconstrainedBox
to grant it the freedom of defining its dimensions.
Open lib/widgets/solar_events_pageview.dart and add the following snippet below //TODO add LimitedBox
return UnconstrainedBox(
child: _EventBody(item: _item),
);
Build and run the app to see it in action.
You should see the familiar white bottom sheet again. Open the Widget Inspector to see why this happens.
One look at the Widget Inspector will tell you UnconstrainedBox
has a ListView
as its child. From earlier experience, you should rightly assume that wrapping the ListView
inside Expanded
is the way. That won’t do in this case, though, because of a limitation of the Expanded
widget. Only flex widgets such as Row
and Column
can have Expanded
widgets as children.
Another layout error occurs if a child of a non-Flex widget has an Expanded
child, producing the message “Incorrect use of ParentData widget”. This error also occurs for Positioned
, TableCell
and Flexible
, which require Stack
, Table
and Flex
widgets, respectively.
You need a widget that sets a maximum height and width for its child in unconstrained environments. LimitedBox
is the answer in this case.
Open lib/widgets/solar_events_pageview.dart and replace the child of UnconstrainedBox
with the following snippet:
LimitedBox(
//1
maxHeight: MediaQuery.of(context).size.height / 2,
//2
maxWidth: MediaQuery.of(context).size.width,
child: FractionallySizedBox(
//3
widthFactor: 0.75,
child: _EventBody(item: _item),
),
),
Here’s what’s happening in this snippet:
- The maxHeight for the
LimitedBox
child is set to half the device height. - The maxWidth for the
LimitedBox
child is set equal to the device width. - Here, you set the widthFactor of the
FractionallySizedBox
. This widget takes the size given by its parent and multiplies it by the corresponding factor. The result is set as a constraint for its own child.
The usefulness of the LimitedBox
widget becomes more evident after you look at its Layout Explorer blueprint, which shows that it sets maximum dimensions for its children when its parent provides it with unconstrained dimensions.