Deep Dive

Flutter Deep Dive Part 1: “RenderFlex children have non-zero flex…

Copy from https://medium.com/flutter-community/flutter-deep-dive-part-1-renderflex-children-have-non-zero-flex-e25ffcf7c272

Part 1: “RenderFlex children…”, wading into the Baby Pool

In addition to speaking about Flutter and embarrassing myself in front of audiences on two continents, I recently decided to do a deep dive. All of my friends do deep dives on cool things like Animation, State Management, Gestures or Testing.

Me? I decided to do a deep dive on an error message.

But this is no ordinary error message. If InheritedWidget is Flutter’s Monad (and it is), then “RenderFlex children have non-zero flex but incoming height constraints are unbounded” is our Boogeyman (Uomo Nero, Бугимен, Hombre Boogeyman, بعبع , Mumus, Croque-mitaine, Baba Yaga, John Wick).

Da Boogeyman (Image CC-0)

It should also be noted that our Boogeyman has a few brothers and sisters you might see now and then:

  • “RenderConstrainedBox object was given an infinite size during layout.”
  • “BoxConstraints forces an infinite width.”
  • And more you’ll be able to recognize after you‘ve read this deep dive.

Relax, all of these errors are nothing more than small variations on a single theme. The error messages are different because in each of these cases the error is being caused by something different but, in the end, it all boils down to the same problem:

Something is trying to be infinitely large, nothing is there to stop it, and you need to fix that.

某个组件想要扩展到无限大,没有限制被施加到他的上面,你需要修复这个错误

Once you understand the way things work under the hood, it gets easier and easier to know exactly what to do when the Boogeyman crawls out from under your bed.

You send John Wick, of course.

Wading Into the Baby Pool

First I want to offer a quick version of this for those who don’t want to dive into deep water. I call it, “The Baby Pool Version”.

Please note that all these code examples are custom formatted to be a little easier to read. I don’t format my working code like this…

RenderFlex children have non-zero flex but incoming height constraints are unbounded.”:

This is a simple example of a broken widget that will cause this error:

Widget brokenCodeRenderFlexError =
  Column(
    /// A Column's default is "mainAxisSize: MainAxisSize.max,"
    /// This means it will try to take all the height it's allowed to.
    children: <Widget>[
      Container(
        height: 300,
        width: double.infinity,
        color: Colors.red,
      ),
      /// The next container is trying to be infinitely large
      Container(
        height: double.infinity,
        color: Colors.green,
      ),
    ],
  );

This Widget Will Cause a RenderFlex Error

What’s happening here is a Row/Column is not being passed size constraints by its parent (“Incoming Constraints are Unbounded”) but its child is trying to “flex” to be as big as possible (“Render Flex Children Have Non-Zero Flex”). The reason this is a problem is that either the parent or child has to tell the Row/Column what size to be, but that’s not happening. The child is saying, “Go to infinity”, and the parent is saying, “Do whatever you want”.

The quick, baby pool fix is to change the mainAxisSize of the Row/Column to MainAxisSize.min, then wrap the child that wants to be infinitely large in an Expanded. The Same Widget, Fixed

Widget fixedCodeRenderFlexError =
  Column(
    /// Fix Part 1: Tell the Column to be as small as it's children will allow
    /// by changing the mainAxisSize: to mainAxisSize: MainAxisSize.min,
    mainAxisSize: MainAxisSize.min,
    children: <Widget>[
      Container(
        height: 300,
        width: double.infinity,
        color: Colors.red,
      ),
      /// Fix Part 2: Now wrap the child that wants to be infinitely large in an Expanded
      Expanded(
        child: Container(
          height: double.infinity,
          color: Colors.green,
        ),
      ),
    ],
  );

BoxConstraints forces an infinite height (or width).”:

Your Row/Column has a child that is trying to be infinite height/width and there aren’t any constraints, which is only a slightly different situation than the one above. One way this can happen is if you set the height/width to double.infinity on something that is inside of a Row/Column. That’s not a bad thing, we do it all the time. However, if your child is in a Row or Column that isn’t limiting the child’s size, now we have problems.

final Widget box_constraints_forces_an_infinite_height_or_width = 
  Column(
    children: <Widget>[
      Container(
        height: double.infinity,
      ),
    ],
  );

Possibly the worst code I’ve ever written, but it makes the point.

This error can be in the mainAxis or the crossAxis:

  • If the problem is in the mainAxis you can fix it by putting the offending child inside of an Expanded.
  • If it’s along the crossAxis and you nested Rows and Columns inside of each other, then you need to put the nested Row/Column into an Expanded instead.

RenderConstrainedBox object was given an infinite size during layout.”:

This one is kind of cute. I forced it to happen like this:

final Widget render_constrained_box_object_was_given_an_infinite_size_during_layout = 
  Column(
    children: <Widget>[
      Row(
        children: <Widget>[
          Container(
            child: SizedBox(
              height: double.infinity,
            ),
          ),
        ],
      ),
    ],
  );

It actually took some effort to break it in just the right way to cause this error…

How do we deal with this? Well, there are a few things you might try, but not all of them would be successful:

  • Giving the Container a specific height will fix it.
  • Giving the SizedBox a specific height will fix it.
  • Wrapping the Container with an Expanded will not fix it. That actually causes a bottom overflow.
  • Wrapping the SizedBox with an Expanded will cause: “Incorrect use of ParentDataWidget. Expanded widgets must be placed directly inside Flex widgets.”

The last error, the one about ParentDataWidget? It means an Expanded can only be used inside of a Row or Column but you tried to use it somewhere else.

So, that was the baby pool. Wasn’t that easy?

Part 2: Taking the Plunge

Welcome to Part 2 of our series on dealing with RenderFlex and other, similar errors in Flutter. Here, we’re going to take a look at the basic “conversation” that goes on during layout, get an overview of Flutter’s “three layer cake” and I’ll give you an introduction to the Widgets that we’ll be digging into a lot more in Part 3.

So grab a drink (and maybe some headache medication) and let’s get started! Taking the Plunge Going Deep (Image CC-0)

In Part 1 we waded into the baby pool, but here we’re going a lot deeper. I can think of no better place to start than by explaining what’s happening in terms that can be understood by a human being.

First, you want to understand there are a few Widgets that rely on others to tell them what size to be. Some of these Widgets are Flex, Column, Row, SingleChildScrollView and List.

首先,需要知道有一些组件依赖于其他组件来推导他自己的尺寸。比如Flex, Column, Row, SingleChildScrollView and List

When laying out UI elements in Flutter, there is a kind of “conversation” that goes on. Let’s take a look…

In our example, we have three Widgets. The first is “Parent”. Parent has both a Child and a Grandparent. Parent is one of the Widgets that need to be told what size to be because, just like a real parent, it has no idea what’s going on.

Grandparent to Parent: “How big do you want to be? You can be as big as you want, all the way to infinity.”
Parent to Grandparent: “Let me get back to you on that.”
Parent to Child: “What size do you want to be?”
Child to Parent: “I want to be as big as I possibly can, make me to the biggest size you can let me be!”

Parent is confused and looks at the ground. Grandparent didn’t tell it what size to be, Child didn’t tell it what size to be either, and the only thing in the try/catch block is a three-month-old comment from Bill about Taco Tuesday.

This is when the Magic Smoke leaks out of your laptop, the screen goes black and your test device displays “the red screen of death”.

What’s happening is the child wants to “flex” to infinity (children have non-zero flex) and the Grandparent says it can be as big as it wants to be (but incoming height constraints are unbounded).

Does this make a little more sense now? Good. Now that you have a grasp of the basics, tie a rope around your waist and let’s really dive down the rabbit hole. Under the Hood

I’m assuming you already know about the Widget, Element and Render layers but, if not, here’s the tl;dr version of “Flutter’s Three Layer Cake”:

The Widget layer contains Widgets, which are like blueprints for whatever you want to make.
The Element Layer is where you find the Elements that are sort of skeletal representations you build from each Widget. They’re like mockups or empty shells. An Element only contains a Type and, if there is one, a Key. During the process of rendering each frame, you’re going to check to see if the Widget for this frame still matches up with the Element from the last frame. If they match, then you can just leave it alone and move on. If they don’t match, then you set a flag for it to be destroyed, so you can build a new one later.
The Render layer contains the objects that Android and iOS devs would most closely associate with “Views” (Android and iOS don’t really have an equivalent to the Widget or Element layers). Render Objects are (basically) created from the Elements and only get changed when needed.

There’s a lot more to it than that but, for this article, we don’t need to go into any more detail. If you want to go digging into the Widget, Element and Render layers, then I suggest you start with “Flutter, what are Widgets, RenderObjects and Elements?”, by Norbert Kozsir. To misquote Shakespeare: “A Rose, is a Rose, is a Turkey Sandwich”

There are three things you want to make sure you understand when dealing with Rows and Columns:

Flex
Flexible
FlexFit

These are easy to keep straight in your head:

One is a multi-child layout widget that takes multiple children.
The second is classified as a multi-child layout widget but actually won’t take more than one child. (Wait, what?!?)
The third determines how the second fits into the first, determining the size of the first. But, because there are outside factors telling it to sit down and shut up, it really doesn’t do much of anything… well, except keep the second from trying to force the first to be bigger than you can imagine (you know, like “the Force” in Star Wars). So, in a way, I guess it does help determine the size of the first… but not really.

See how easy that was!

</sarcasm alert>

(That’s a joke, people… It’s confusing as hell.)

Part 3: A Flex is not a flex

Part 4: The Flex Layout Algorithm