Some words about rendering
Learning objectives
- You know the basics of how the size of a widget is determined.
- You know how to use widgets like ListView in Row and Column.
Here, we will briefly look into how the size of a widget is determined. The following example shows an example of using ListView
to show content to the user.
If we add the ListView
as a child of a Row
widget, something odd happens, however. Try the following example.
class HomeScreen extends StatelessWidget {
Widget build(BuildContext context) {
return Row(children: [
ListView(children: const [
Card(child: ListTile(title: Text('Hello'))),
Card(child: ListTile(title: Text('world!'))),
])
]);
}
}
When we launch the above program, magic! The application crashes!
In reality, there's no magic. When we look into how Flutter renders widgets, we see that it has a two-step process. First, Flutter passes constraints in the widget tree from top to bottom. For example if a Container
has a width of 200 and a height of 100, then that width is treated as an incoming constraint by the children of the Container
. The following application shows a ListView
in a green Container
. When you try it out, you observe that the ListView
does not fit into the given constraints and thus the ListView
needs to be scrolled.
Second, Flutter passes widget sizes in the widget tree from bottom to up. For example, if a Container
has the height of 100, but no width, then the child widget can influence the width of the parent container (given that the parent container is not constrained by its parents). In the following, we have modified the previous example by removing the height of the Container
. Now, when you try out the application, the container fills the height -- this is due to the ListView
, which tries to maximize its space.
Note that containers with children size themselves to their children. Other widgets can behave differently. For example, in the following, the height of the container will be constrained to the height of the Text
widget.
To summarize, when Flutter infers widget sizes, the widget tree is traversed from top to bottom to provide constraints from parents to children, and from bottom to top to provide sizes of children to parents. This, for example, leads to a situation where a Row
widget has a width -- the width is determined by the children.
Why then did we not see the ListView
within the Row
? Let's try out the following component with Row
and ListView
again.
Still not working. However, when we look into the browser console (when testing the application in a browser), we see a set of errors, including the following lines.
The following assertion was thrown during performResize():
Vertical viewport was given unbounded width.
Viewports expand in the cross axis to fill their container and constrain their
children to match their extent in the cross axis. In this case, a vertical
viewport was given an unlimited amount of horizontal space in which to expand.
The relevant error-causing widget was:
ListView ListView
Let's consider again what happens when Flutter determines the size of the widgets for the above application. A Row
widget attempts to maximize the width (giving no constraints on width -- or more specifically an infinite width -- to the children). ListView
, however, also wants to be as wide as possible, reserving as much space for its children -- in part as it is lazily created. This leads to a situation where the Row
provides a possibility for infinite width, and ListView
claims infinite width.
The solution to the problem is to restrict the ListView
from claiming the infinite width that Row
provides. This can be achieved by wrapping the ListView
with e.g. Expanded
that will claim only all the space on the screen or with a Container
with a specified width, as shown in the following example.