skip to Main Content

Prelude

I’m familiar with the dictum

"Constraints go down. Sizes go up. Parent sets position.

What I’d like to understand is how "constraints" are defined. Is it just a range ("height must be between 30 and infinity"), or is there something more elaborate? Surely I can find the answer from reading Flutter’s code, but I’m not yet at that stage.

A concrete answer addressing a very specific example is much more helpful than hand-waving.
Hence in an effort to avoid vague answers, please address the following concrete example.

Question

Why does uncommenting the commented-out line trigger an error?

import 'package:flutter/material.dart';

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          // crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Text('Hello),
            Text('World),
        ]),
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Text('How),
            Text('are you?),
        ]),
      ]
    );
  }
}

Clarification

The following is another way of asking the question.

In the three part:

  • "Constraints go down."
  • "Sizes go up."
  • "Parent sets position."

it’s clear what "size" is. It is the Size class.

It’s also clear what "position" is. There is also a class for that (Offset).

  1. Is this the class that captures a "constraint"? Is it visible from the programmer side?
  2. How does the instance of that class change when we uncomment the commented-out line (thereby triggering the error)?

2

Answers


  1. According to the documentation of the row: https://api.flutter.dev/flutter/widgets/Row-class.html , the first step of the render is Layout each child a null or zero flex factor (e.g., those that are not Expanded) with unbounded horizontal constraints and the incoming vertical constraints. If the crossAxisAlignment is CrossAxisAlignment.stretch, instead use tight vertical constraints that match the incoming max height.

    And the fourth step: The height of the Row is the maximum height of the children (which will always satisfy the incoming vertical constraints).

    Since you’re not specifing any container or "wrapper", Flutter doesn’t know what height ot put on the row when you’re stretching the crossAxis.

    In your example:

    Let’s say you wrap the first wor in a Container with 400 height and the second text also inside a Container to see what’s happening when stretching to minimun and maximun possible height:

    Container(
          color: Colors.white,
          height: 400,
          child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: [
                Text('Hello'),
                Container(
                  color: Colors.green,
                  child: Text(
                    'World',
                  ),
                ),
                Container(
                  width: 40,
                  height: 20,
                  color: Colors.blue,
                )
              ]),
        )
    

    Result:

    enter image description here

    But if you use another variation: center, start, end, the containers would wrap to its minimum:

    crossAxisAlignment: CrossAxisAlignment.center:
    

    enter image description here

    crossAxisAlignment: CrossAxisAlignment.start:
    

    enter image description here

    crossAxisAlignment: CrossAxisAlignment.end:
    

    enter image description here

    Login or Signup to reply.
  2. Constraints for a widget are defined by 4 doubles, min and max width, min and max height, with infinity as a possible value.

    Now on your question, setting CrossAxisAlignment to Stretch, causes the constraints passed to the children to be tight in the cross axis.

    A tight constraint in flutter is when, the parent offers to its child only ONE size, for its width and its height, so the min and max width will be equal, same goes for the min and max height.
    On the other hand, a loose constraint only sets the max width and the max height, but the child can be as small as it wants.

    When the parent gives a constraint, that is loose and infinite on a specific axis, the child must give back a size for that axis that is NOT infinite, otherwise Flutter will throw BoxConstraints forces an infinite height.

    For example, if you have a Column, that gives infinite height to its child ListView, the ListView, if not wrapped by a specific widget, will throw, as it’s trying to give back an infinite height to the parent,
    in that case, you can either wrap the ListView with an Expanded widget, or a SizedBox.

    In your example, this is what is happening:
    Your Column widget, is giving the following height constraints to its 2 children (the 2 Rows)

    • min height : 0, max heigth: infinity.

    These are loose constraints, so the children can take as much size as they need.
    And each of those Rows then, passes the same constraints to its children (the 2 Text widgets), and again, the constraints are the same and loose,
    so the Text widgets can be as tall as they want.

    As soon as you set the CrossAxisAlignment to Stretch, the constraints that the Row is passing down are tight, this means that when the parent Column, asks the child Row that has Stretch, what size it wants to be,
    the Row will answer with infinite (always referring to the height), and this will throw the error, as said before, if the constraint set by the parent is already infinite, the size the child wants cannot be infinite as well, on that axis.

    So to solve this, when a child is setting a tight constraint for its children, and the parent of that child is giving infinite constraint on a specific axis,
    the child MUST be either wrapped in a Flex widget (Expanded, Flexible) or it must have a finite size (e.g wrapping it with a SizedBox).
    So for your example, if you wrap your Stretch Row, in either a Flexible or Expanded widget, your error will go away:

     return Column(
      children: [
        Flexible(      /// this fixes your issue
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              Text('Hello'),
              Text('World'),
          ]),
        ),
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Text('How'),
            Text('are you?'),
        ]),
      ]
    );
    

    To sum up, if a parent Widget gives, on a specific axis, infinite contraint, then the child, cannot ask to have its size, on that specific axis, infinite as well, because, if there are multiple children, the following one won’t have any space left to size themselves.
    In this case, the child MUST be either wrapped with a sizing widget, or with a flex widget.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search