When we start to learn flutter from simple layout examples, everything works as expected. The widgets are aligned and sized properly.
But once our application starts to grow especially when we have more dynamic UI, we start to see a lot of layout errors and issues.
We can find temporary solutions on StackOverflow but deep inside we know something is missing, something about the layout constraint system that we don’t understand.
Flutter constraints follow this simple rule.
Constraints go down. Sizes go up. Parent sets position.
This is my attempt to explain how the flutter constraints work and how we can use them in our day-to-day flutter development.
The series is divided into three articles based on the above rule, explaining each rule with examples, common errors, and its solutions.
So Let’s start with the first rule “Constraints go down”
What are constraints?
Constraints is limitation or restriction set on you by someone on top of your hierarchy.
- A boss sets 9-5 job timings on its employee to be in office.
- Parents allowing kids to playout outside for 2 hours from 9 am-11 am.
The bold text in the above example are the constraints.
Similarly, In Flutter everything is a widget. The widget is nothing but a box. Now how big or small this box can be drawn on the screen is defined by an object called
BoxConstraints is nothing but an object having four values i.e
maxHeight. The below example shows the yellow box taking its minimum and maximum size based on
Let’s write some code.
Let’s create the above yellow box using a
Container that is wrapped inside ConstrainedBox which defines the maximum and minimum size of the
Note: We can use the
constraints parameter in
Container itself. But to showcase the parent-child relationship for the sake of this example we are wrapping inside
Hmmm…This is not what we expected.
The yellow box size should be of the constraints given by the
ConstrainedBox, but it’s taking the full screen?
The first thing we usually do is to google “Why the given constraint is not working in flutter”?
The top answer will say “Wrap this widget with
And we make that change at line 10
And Boom!!! It is working now as expected.
Now the question is What happened?
Beginner developers don’t bother to ask this question and move on.
But someday or the other we will fall into this trap again.
So before we explain the solution first let’s understand…
What does the rule “Constraint go down” mean?
Flutter UI is based on render tree having parent-child relationship between widgets. The root widget is Parent and can have children and those children become parent if they also have childrens.
In our case, the tree looks like this.
Now the constraint rule says that, only the parent widget can pass constraints to its child. Not the other way around.
Based on that, a child widget can decide its size within the given constraint by its parent. (More on this in part 2)
So from the Tree perspective, the Constraint always goes in the down direction and never upwards.
Types of box constraints
To understand the above solution, we need to understand what types of box constraints are passed between widgets in the tree.
There are 3 types of box constraints Tight, loose and unbounded.
Let’s understand these by taking a real-world example from a different kind of parenting.
1. Tight constraints → Authoritative parenting
These kinds of parents are very strict.
For example, Parents who have decided fixed career for their children regardless of children’s personal interests.
In this situation, most children don’t have any option and are forced to pursue a career which their parents want.
In Flutter, this parent-child widget relationship is called tight constraints where a parent widget passes a tight constraint to its child widget, and the child widget is forced to take size based on parent constraints while ignoring their own size.
In code, we call it tight constraint when we set
minWidth == maxWidth and
minHeight == maxHeight. We can use
BoxConstraints.tight() constructor for this by passing a
Size object as a parameter.
2. Loose constraints → Permissive parenting
These kinds of parents are very lenient and supportive.
Instead of forcing a child to pursue one career, they came up with a list of different careers.
In this situation, the child has the freedom to choose a career from the given list. But they cannot choose a career outside the given list.
In Flutter, this parent-child widget relationship is called loose constraints where a parent widget passes a range of sizes to the child widget.
The child has the option to choose a size from that range. It can be big or small within the given range. (Know more about how a child chooses its size. Check out the second part of this article)
In code, we call it loose constraint when we set
minWidth == 0 and
minHeight == 0. We can use
BoxConstraints.loose() a constructor for this by passing an
Size object as a parameter.
3. Unbounded constraints → Uninvolved parenting
These parents generally stay out of the way of their child’s choices.
In this situation, the parent does not have any say in the child’s career choice which gives a child full freedom to choose a career based on their interest.
Sometimes this leads to a wrong career choice if there is no proper guidance. So we need to be careful about this.
In Flutter, this parent-child widget relationship is called unbound constraints where a parent widget passes unbound size (No restriction on the size) to child widget. The child widget can choose any size. It can also choose size beyond its parent constraint which sometimes leads to an
Overflow pixels error on the UI. So we need to be cautious while using unbounded constraints.
In code, we call it unbounded constraint when we set any of the size values to
double.infinity. We can use
BoxConstraints.expand() a constructor for this.
The unbound constraint can be specific to one direction also. Let say if we have a fixed height but the width is
double.infinitythen we called it an unbounded width. The same goes for height as well.
An unbounded constraint can also be a tight or loose constraint at the same time if you set the minWidth to
double.infinity or to zero respectively
Going back to the problem
So can you guess by now what’s wrong with the first example?
Its because the flutter framework is passing a tight constraint to
MyApp() and then
MyApp() is passing that tight constraint to
ConstrainedBox which forces
ConstrainedBox to ignore its own constraint and forced to use its parent constraint i.e the full-screen size. Hence we see yellow on full screen.
Now the next question would be….
Then how does the
Center solves the problem?
In the above example, we saw 3 types of parenting. But as we know in real-world there are some outlier children who
- Pursue a career on what they are interested in regardless of their parent interest and give the same choice to their child.
- Give more options to their child than what their parents gave to them.
- Or for worse, they enjoy the freedom of the there choice but force their child to pursue a specific thing.
In this case,
Center is that outlier child which takes a tight constraint from
MyApp() and converts it to loose constraint for its child
ConstrainedBox and hence
Container follows constraints given by
If you want to know more about how widget sets the size as
Container does. Check the second article of this series.
If you want to know more about how the widget sets the alignment as
Center does. Check the third article of this series. (Coming soon)
Common widget constraint problems and solutions
Error: BoxConstraints forces an infinite width and infinite height.
This happens when the parent passes unbounded constraints i.e a height/width of size
double.infinity and its child tries to be as big as the size
double.infinity then it would result in the above popular error.
We can demonstrate this error by wrapping a
Container in an
UnconstratinedBox, and then setting the Container’s height or width to
doube.infinty or by using
Because Flutter cannot render infinite sizes. Either the parent or the child has to set a bound size so that the framework knows the size it needs to render. To solve this problem we can use widgets that have bound constraints. For example LimitedBox.
- A widget gets its own constraints from its parent. A constraint is just a set of 4 doubles: a minimum and maximum width, and a minimum and maximum height.
- Then the widget goes through its own list of children. One by one, the widget tells its children what their constraints are (which can be different for each child),
In Flutter, all widgets like Scaffold, Row, Column, Expanded, Flexible widget render themselves based on the box constraint either set by their parents or by themself. Due to this rule, we have some limitations on widgets.
- A widget can decide its own size only within the constraints given to it by its parent. This means a widget usually can’t have any size it wants.
- If a child wants a different size from its parent and the parent doesn’t have enough information to align it, then the child’s size might be ignored. Be specific when defining alignment.
That’s it for the first rule folks!!
Resources and Credits
Most of the examples and understanding of these rules are based on these two articles. I highly recommend going through this once at least.
- From the official flutter, doc check out Understanding constraints.
- A Deep Dive Into Flutter Box Constraints by Naresh Idiga
If you have any questions please let me know in the comments.
Stay tuned for the remaining part of the series and to get notified first, subscribe to this blog.