What Is a Render Object?
The most powerful object in Flutter

RenderObjects are the most powerful, versatile, and complicated objects in a Flutter user interface. RenderObjects play a number of roles in the Flutter Pipeline, along with Widgets, Elements, and Layers. It’s important to understand the Flutter Pipeline at a high level, before you try to understand the responsibilities of RenderObjects, specifically.

Let’s look at a RenderObject’s responsibilities.

Layout

A RenderObject is responsible for layout. Layout means choosing a size, and positioning children.

Not all RenderObjects have children. A RenderObject with no children is referred to as a “leaf” RenderObject. A leaf RenderObject only needs to choose its size during layout.

An example of a RenderObject with children is a column. The column’s RenderObject places its children in a vertical list, taking up all space within the column. Most layouts can be accomplished with Widgets in the Flutter framework, e.g., Row, Column, Stack, Center, Align. However, special use-cases might require a layout policy that Flutter doesn’t support out of the box. In that case, you may need to implement a RenderObject that positions its children as desired.

Before you implement a RenderObject for layout, see if a CustomMultiChildLayout might handle your needs.

Learn more about Render Object layout.

Hit Testing

A RenderObject is responsible for determining when a tap or click falls within its bounds. A RenderObject is also responsible for deciding whether or not that tap or click is allowed to pass through to other RenderObjects behind it. For example, a red rectangle might absorb the hit, while a transparent rectangle might let the hit pass through to whatever sits behind it.

In most cases, a GestureDetector widget will provide all the hit testing and gesture handling behaviors that you need. But if you’re implementing a RenderObject for other reasons, such as layout or painting, then you’ll need to be sure to include the right implementation for hit testing, too.

Learn more about hit testing with Render Objects.

Painting

A RenderObject is responsible for painting the content on the screen. Every piece of content that you see in a Flutter UI is painted by a RenderObject. This includes every shape, image, and text-run. If you’ve only ever worked with Widgets, then you may have believed that Widgets paint the content on the screen. But that’s incorrect. Widgets are merely configurations for RenderObjects. Your Widget trees create render trees, filled with RenderObjects, which paint the screen.

Painting is accomplished with a combination of two tools: Canvas painting and Layer pushing.

Canvas painting is the most common and easy-to-understand path to putting content on the screen. Every RenderObject is given access to a Canvas in the RenderObject’s paint() method. The Canvas object offers methods like drawRect(), drawOval(), drawImage(), and drawParagraph(). These draw methods are self-explanatory.

@override
void paint(PaintingContext context, Offset position) {
  // Paint a red rectangle that sits at the desired `position` and
  // takes up the desired `size` of this RenderObject.
  context.canvas.drawRect(
    Rect.fromLTWH(position.dx, position.dy, size.width, size.height),
    Paint()..color = Colors.red,
  );
}

Sometimes you need control over visual composition in a way that a Canvas can’t help you. Perhaps you need to create a dividing point in your painting so that part of your painting re-runs, but the other part doesn’t. Or, perhaps you need to paint a texture to the screen. Or, perhaps you want to blur all the content behind your RenderObject. To accomplish these specialized tasks, you need to take advantage of an even lower-level part of Flutter called the “layer tree”. The layer tree, as the name implies, is composed of Layer objects. RenderObjects can “push” layers using the PaintingContext that they’re given in the paint() method. Understanding layers is beyond the scope of this guide, but keep in mind that RenderObjects are responsible for pushing layers onto the layer tree, and this is part of painting.

@override
void paint(PaintingContext context, Offset position) {
  // Display a texture by pushing a TextureLayer onto the layer tree.
  // The texture is scaled and distorted to fit exactly the `size` of 
  // this Render Object.
  context.pushLayer(
    TextureLayer(
      textureId: myTextureId,
      rect: Rect.fromLTWH(position.dx, position.dy, size.width, size.height),
    ),
  );
}

If the only thing you need to access ia a Canvas, consider using a CustomPaint widget instead of implementing a RenderObject. You’ll get the same capabilities with a tiny fraction of the effort.

Learn more about painting with Render Objects.

Children

Managing children is a RenderObject’s most complicated responsibility.

Some RenderObjects have a single child. Some RenderObjects have a few children with specific names, e.g., appBar, drawer, floatingActionButton. Some RenderObjects have an entire list of arbitrary children, e.g., a Column, Row, or Stack. Finally, some RenderObjects have a combination of these child organizations.

Coming from a Widget perspective, it might seem strange that managing children would be a complicated responsibility. After all, managing children in the widget tree is extremely easy. Why is it so hard in the render tree?

Unlike Widgets, RenderObjects are mutable, which means they change over time. Therefore, you can’t just throw away an old list of children and replace it with a new list of children. Instead, a RenderObject needs to remove specific children that are no longer desired, and add new children that are desired.

[INSERT IMAGE SHOWING SOME CHILDREN BEING REMOVED AND OTHERS ADDED]

Additionally, a RenderObject has to implement the storage for children. The storage doesn’t need to be complicated, but it’s still something that a RenderObject needs to include. For example, if a RenderObject has a list of children, the RenderObject needs to decide whether to store those children in a List, or implement a linked list, or store them in a Map, or perhaps a custom data structure. This choice depends on when, where, and how you expect your children to be added and removed over time.

[INSERT IMAGE SHOWING DIFF DATA STRUCTURES HOLDING CHILDREN]

Lastly, there are various contractual obligations that RenderObjects have when managing children. For example, every RenderObject has a method called visitChildren(). This method is expected to run a callback for every one of its children. If this method isn’t implemented correctly, it completely breaks the ability for the Flutter Pipeline to use your RenderObject. Similarly, a RenderObject is responsible for attaching its children in attach() and detaching its children in detach(). A Render Object must ensure that all protocol expectations are implemented, with regard to children.

[INSERT CODE SAMPLE WITH CLASS OUTLINE SHOWING ALL CHILDREN METHOD SIGNATURES]

The organization of children within a RenderObject is called the “child model”. There are a few child models that are very common. To help developers implement RenderObjects with these common child models, the Flutter framework provides mixins that handle most of the protocol implementation details on your behalf.

Checkout RenderObjectWithChildMixin for single-child RenderObjects. Checkout ContainerRenderObjectMixin for RenderObjects with a list of arbitrary children.

Learn more about managing children in a Render Object.

Relationship to Widgets and Elements

In a typical Flutter UI, RenderObjects don’t exist on their own. Instead, a RenderObject is associated with a Widget and an Element. I call this Flutter’s Holy Trinity.

A Widget holds the desired configuration for a RenderObject. A Widget also creates the desired Element and RenderObject. An Element rebuilds its Widget, when needed, and manages the relationship between the Widget and RenderObject. Finally, a RenderObject has its properties updated based on its Widget, and a RenderObject has its children provided to it by its Element.

[INSERT IMAGE OF TRINITY]

The relationship between Widgets, Elements, and RenderObjects is complex, and it can take a while to understand. But after all the Widgets, Elements, and RenderObjects do their dance, your Flutter UI ends up with an entire widget tree, element tree, and render tree, all intermingled, together.

[INSERT IMAGE OF TREES INTERMINGLED]

These relationships are what allow you to declare simple Widget constructors that paint complex, dynamic content.

Learn more about Flutter’s Holy Trinity.