Hello to all Flutter developers and people who are only familiar with this technology. My name is Denis and I am a Flutter developer at AppVesto. In this article I will talk about such a topic as a dynamic user interface, which adapts itself to the size of screens on different devices. In the case of an amateur or training project, this topic is not so important because of the small number of target devices on which the application will be used. At the same time, on a full-fledged corporate project, UI responsiveness is one of the main topics and problems simultaneously.
Not every customer initially provides a list of supported devices, and some put in the project support for tablets or other specific screens. Flutter, in turn, provides a wide enough functionality to create an adaptive UI, which can easily confuse a developer who has only recently started to learn Flutter, or has not previously worked with similar topics. Besides “out of the box” approaches, this article will cover some packages that can help with adaptive interfaces.
“Wide functionality” in the case of Flutter really means a very large number of features and options for their use. Also, different approaches have different effects on the layout, both on the code side and the directly visible interface side. Accordingly, it makes sense to conditionally divide the “adaptive” functionality into several approaches:
- The Flex approach,
- Screen Util,
- Layout Builder.
Due to the breadth of the topic and diversity of approaches, the article is divided into several parts. In each part we will consider all approaches separately, analyze their advantages and disadvantages and study simple examples for each of them, respectively. In addition, I will consider some nuances and “chips” when working with adaptive UI. Well, here we go.
The fit parameter is also very simple — only FlexFit.tight and FlexFit.loose can be passed into it. Accordingly Flexible will take up all available space (tight) or maybe less available space (loose). Flexible is quite rare in its pure form, usually Expanded and Spacer are used.
The first thing we need to use the Flex approach is the most common Column or Row. As an example, let’s take a very simple case, for the sake of clarity, a pair of Container widgets in the Row with an indent between them and indented edges.
First of all, for this we need to wrap both containers in Expanded. Of course, similar behavior can be recreated using all well known parameters of mainAxisAlignment/crossAxisAlignment, but AxisAlignment in most cases works with widgets that are static in size and are more like an auxiliary fitting. In our case, containers are not sized at all — their size is redefined by the parent Expanded widgets. The result should look something like this:
Pretty simple, isn’t it? The flex parameter is responsible for the size of spacers and expanders, more precisely, for their proportion. By default, all flex widgets have a flex equal to 1 unless you set a different value. Accordingly, in the example above, each of the expanders will be three times bigger than any spacer, and the spacers are equal to each other (like expanders).
If we explain the work of the flex-parameter using the analogy with a simple example from real life, then the bar or cooking is ideal, where the recipes are described in the format “3 parts X, 2 parts Y, 4 parts Z”. In our case it would sound like this (from top to bottom): “1 part of Spacer, 3 parts of Expanded, 1 part of Spacer, 3 parts of Expanded, 1 part of Spacer”.
It would seem what could go wrong? Strange as it may seem, the Flex approach has quite a few fundamental problems. First of all, Container, “crimped” by performers, takes the size of a child widget, and Container in Expanded takes the size set by Expand. Secondly, if we simply add our Row with containers to a dimensionless Column, the Row will shrink to an incorrectly small size or disappear altogether.
So why did it happen? The fact is that Expanded/Flexible wrapped in Row reassigns only the width, and wrapped in Column — only the height, respectively. That is, we should use static dimensions on one of the axes or wrap our Row in another expander and additionally work with the column flex. This significantly complicates the work and “opens up” a lot of nuances.
Let’s consider one more example of incorrect functioning. In the past, a piece of TestContainer code had no default height. In this example, let’s set it to 100 by default and wrap Row in Expanded and Solumn. In addition, let’s add Spacer to the column below and in Expanded let’s give flex equal to two.
Explaining what is happening is quite simple. As I said earlier, Expanded/Flexible in Row reassigns only the width, which is what happened in this situation because we added the default value to the container. Fixing it is easy enough to remove the parameter or pass null into it.
In addition, the Flex-approach is badly “friendly” with scrollable widgets, due to the fact that the Flex-pattern usually has no or almost no static size, and scrollable in turn requires a static size for child widgets. This leads to an error when scrollable can not get the actual size and takes infinite sizes, thus breaking the application.
It’s time to sum up a little bit on the Flex-approach
- Full adaptability,
- No third-party packages are required,
- Flexibility and versatility of approach,
- Practically there is no redundancy from the code side.
- Does not interact well with scrollable,
- High labor costs,
- A large number of “pitfalls”,
- The approach is considerably more complex if dynamic dimensions are required on both axes at once.
In the next part, we will look at a package such as ScreenUtil and analyze it by analogy with the Flex approach.
Also at the end of each article I will leave a simple example on my repository. The examples use the package DeviceSimulator as it is ideal for demonstrating the UI on different devices. The only thing you need to know about it is to run the example on a tablet (real or emulator) with the largest possible screen size to function properly.
I will wait for your feedback. If you like the articles, then in addition to the planned parts, I will also do an additional article where I will separately consider the different implementations of adaptive texts.