The Flutter’s weapons to layout them all — Part 1

Eric Taix
10 min readFeb 8, 2024

--

One Widget to layout them all

Flutter has a lot of layouts to build amazing UI. But how can you build specific layouts for a particular requirement? This serie of 3 parts, will try to answer this question:
— Part 1 : Understand general concepts of layout in Flutter (this part)
— Part 2 : Implement a specific layout widget, in an easy way (soon)
— Part 3 : Implement the final neat UI with interaction (soon)

At Digit RE Group, a French real estate digital and technological division company at Montpellier, we’ve choosen Flutter as the main toolkit for web & mobile frontend development. But some features required us to dive into the depths of Flutter to take advantage of it and offer our users an unique experience. This is our journey and what we’ve learned. Please note that for privacy, all names, avatar, bonuses, turnovers are random values.

Digit RE Group is always looking for developers, see our open positions.

Context

Before diving into this article, let me first expose the business context and what we’ll build.

In the world of real estate some advisors can manage a team of two or more advisors and these advisors, in turn, can also lead their own team and so on. Thus an advisor can manage, consequently, a tree of advisors which is a Directed Acyclic Tree with only one parent per vertex (each tree has a limited depth, defined by a business rule).

This mode of operation is very important because each advisor, in addition to a bonus on the sales he/she makes, receives a percentage of bonus on the sales of his n-1, another different percentage of bonus on the sales of his n-2, etc...

So one of the important feature of our tool is therefore to be able to effectively manage a team as a whole, whatever the level of depth. It is also necessary to provide relevant indicators to the end user in a simple and visual manner, which the design team created through the following UI:

A real estate advisors tree, with edges representing bonuses (amount & percentage)

Even if it’s not obvious in the previous picture, here are some requirements:
1- Cards will have the same width and height
2- A click on the grey arrow will open / close a subtree
3- Hovering over an edge displays the detail of the bonuses / turnover
4- Ability to pan / zoom if the tree does not fit into its container

Understand Flutter Layout

Everything is a Widget

As you now know the context and our UI goal, let’s first recap what we know about Flutter.

This is probably the first sentence you’ve ever heard about Flutter: “Everything is a Widget”. Even if it’s not entirely true, what does it mean?

This means that wether you want to draw something on the screen (a text, an image, …), add some padding, use an animation or layout some children widgets, you add a Widget in the widget tree in a declarative way.

In Flutter each widget is built by composing other widgets, which are themselves built out of progressively more basic widgets. For example, Padding is a widget rather than a property of other widgets. As a result, user interfaces built with Flutter consist of many, many widgets.

The widget tree is built with many widgets to compose the UI

Flutter uses an aggressive composability pattern which is one of the most distinctive aspects of Flutter

Layout performance considerations

With a very large number of widgets, the key to good performance is efficient algorithms. And one of the most important point is the performance of layout which is the algorthim that determines the geometry (size and position) of the whole widgets.

To achieve this performance, the layout algorithm aims for linear layout — O(n) — for initial layout. The principle is therefore to traverse the widgets in a single pass (it only traverses the tree once).

To learn more about Flutter architecture, you can read Inside Flutter.

This principle can be resumed in the, also well-known, following sentence:

Constraints go down, Sizes go up, Positions are set by parents.

Constraints go down and Sizes go up

Each parent asks to its children which size they’d like to have regarding constraints (width and height). When all children have returned their own size, then the parent can determine its own size and position its children.

This is one of the golden rule, to understand how Flutter works and how to use it efficiently. Let’s dive into this rule with the folllowing not so trivial example which involes a Row (a Flex widget) with some Expanded (a Flexible widget)!

A Flex widget example with some Expanded children

For the sake of simplicity in this example, we’ll consider the height as fixed and focus only on width

“Row”: Hey Child1, you can take up to infinity, which size would you like?

“Child1”: Well, I’m a fixed width widget, I’ll take 100 pixels

“Row”: Hey Padding, you can take up to infinity, which size would you like?

“Padding”: Hey Child3, you can take up to infinity, which size would you like?

“Child3”: Well, I‘m a fixed width widget and I’ll take 60 pixels

“Padding”: Ok Child3 you’ll be at 20px left. Hey Row, after asking to my child, I’ll take 100 pixels.

“Row”: Hey Child2, you’re tight for 400 px, which size would you like?

“Child2”: Well, I’ll take 400 pixels

“Row”: Hey Child4, you’re tight for 200 px, which size would you like?

“Child4”: Well, I’ll take 200 pixels

“Row”: Child1 you’ll be at 0 px left, Child2 you’ll be at 100 px left, Padding you’ll be at 500 px left and Child4 you’ll be at 600 px left

A Flex widget (like Row and Column), starts to layout first all non Flexible widgets passing no constraint (infinity) on its main axis. When it received their sizes, it computes the free available space (i.e. 800px-2*100px=600px) and the space per flex (i.e. 600px / (2 + 1) = 200px) and ask the size of flexible widgets passing a tight constraint which matches their flex value.

The parent decides the child’s position in the parent’s coordinate system (that’s why the Padding positioned the Child3 at 20px), the global position being determined during the rendering phase. Note that the child’s layout cannot depend on its position, as the position is not determined until after the child returns from the layout. As a result, the parent is free to reposition the child without needing to recompute its layout.

This is how a Flex widget works, but each layout widget has its own way to pass constraints, compute its own size and position children. There’s no one way to do it: it is the responsability of the layout widget, to provide its own algorithm.

Note about geometries: In Flutter there are multiple kind of geometries which are known as Geometry protocols. A protocol is the way a parent and its children discuss together to agree on size according to constraints. In this serie, I’ll only use the box layout protocol (which is a kind of min/max width/height) but there’re other protocols like silver protocol.

The pixel overflow issue

You have probably already encountered this issue, as this is one the most encountered issue with layout in Flutter: “Overflowed by X pixels”.

The pixel overflow issue

Remember how Flutter layout works? It walks the tree in a depth-first traversal. If we look at the widget tree of the previous image, it could look like this:

In debug mode the yellow stripped rectangle shows pixels which overflowed

In this case, the Row widget doesn’t constrain the size of its children, nor does the Column widget. Lacking constraints from its parent widget, the first Text widget tries to be as wide as all the characters it needs to display. The self-determined width of the Text widget then gets adopted by the Column, which clashes with the maximum amount of horizontal space its parent, the Row widget, can provide.

To fix this, you just need to wrap the Column with an Expanded widget. So the width of the Column will be constrained by its parent because the Row always arranges its Flexible children last and giving the remaining space as a constraint. And in turn, the Column will limit the width of the Text by giving it the same constraint, which will force the Text to be on 2 lines.

Flutter Layout Catalog

So now that we have a better understanding of how Flutter layout works.

Let’s now dig into the Flutter layout catalog (especially multi child layout) to see if we can find some widgets which can fit our layout requirements. For a complete overview of the built-in layout catalog see Flutter Layout catalog.

Row / Column widgets

Intuitively our UI seems to be like a Row & Column mix layout (Table ?) layout. But if you look in details, there are some specific requirements :

  • Some cards are not verticaly aligned (if the number of children is not odd)
  • Spaces between 2 cards are differents if they do not belong to the same parent
  • Some edges will overlap if the destination cards belong to the same parent and if they are in the same vertical direction relatively to the parent vertical position
Specific requirements for the tree representation

And if you look closer how Row, Column or even Table work, you will see that they align their children on their main axis or in 2 dimensional axis for Table or GridView. But it’s not possible to half align 2 children or to overlap 2 or more children.

Row, Column and Table

Columns and rows are widgets often used to arrange children. But they are only made to align children on the same axis. Tables and Gridviews align on 2 axes but lack flexibility.

Stack

Stack could have been a good candidate as this widget layout its children by using a different layer for each of them (so there’s no issue to overlap 2 children). And another interesting caracteristic is that you can position each child using a Positioned widget which can be related to top, right, bottom or left side of its parent.

The Stack widget uses a different layer per child

This could have worked but the biggest issue is that the Stack widget’s size only depends on non positionned children and the StackFit property. Positionned children of a Stack do not factor into determining the size of a Stack. If there are only positionned widgets then the Stack will take the biggest constraints as its size.

And in our case we’d like that our TreeGraph widget‘s size fits all its children. Why? This way we’ll be able to insert our TreeGraph into a InteractiveViewer so the user will be able to pan or zoom if the graph does not fit into the interactive viewer.

Despite its flexibility of use, the stack cannot be used in our use case due to its lack of being able to return the appropriate size according to its children

Others layouts

There are many other layouts in Flutter (Wrap, Flow, ScrollView, …) but I will not describe them as none are able to layout child widgets as we need, and this is out of scope of this serie.

Conclusion

There’s no built-in layout widget which is natively able to layout our real estate advisors tree graph as we’d like to. We need to find another way to implement our specific layout which will be described in the part 2 of this serie!

What we’ve learned in this part?

  • 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.
  • A widget can’t know and doesn’t decide its own position in the screen, since it’s the widget’s parent who decides the position of the widget.
  • Since the parent’s size and position, in its turn, also depends on its own parent, it’s impossible to precisely define the size and position of any widget without taking into consideration the tree as a whole.
  • The size of a Widget can depend on many things: children sizes, constraints set by its parent and multiple other parameters.
  • The Flutter layout catalog is awesome but this catalog fits 90% of our needs not 100%, and sometimes you can have some requirements which need that you write your own layout

In the next part, we will implement a specific layout widget, in an easy way: Flutter weapons to layout them all — Part 2

Thank you for reading. Don’t forget to clap 👏 if you like this article or learn something. This motivates us to write more articles 😃

Thanks to Franck Gil and Michael Chevallier to have reviewed this serie.

--

--

Eric Taix

Fullstack freelance, former co-leader of JUG Montpellier. I believe in communities and sharing knowledge to move forward. In love with #flutter