skip to content

In Flutter, how do ComponentElement and RenderObjectElement differ, and which kinds of widget create each one?

level: middleimportance: should knowfreq 32%

answer

  1. builds a child vs owns a render object
  2. Stateless, Stateful, Proxy elements
  3. Leaf, SingleChild, MultiChild widgets
  4. updateRenderObject on update
  5. render tree skips component nodes

basics

~20 s

A ComponentElement builds one child widget and owns no render object; stateless, stateful and inherited widgets create one. A RenderObjectElement owns a render object and keeps it in sync; Leaf, SingleChild and MultiChild RenderObjectWidgets create one.

solid answer

~40 s

**`ComponentElement`** is the composing kind: its job is to call `build` (or return its proxy child) and manage the single child element that results. `StatelessElement`, `StatefulElement` and `ProxyElement` (used by `InheritedWidget` and `ParentDataWidget`) are its subclasses, so `Container`, `Text` and your own widgets land here, and none of them owns a render object. **`RenderObjectElement`** owns a render object: on mount it calls `createRenderObject`, on update `updateRenderObject`, and it inserts, moves and removes child render objects as child elements change. Its widget side comes in three shapes: `LeafRenderObjectWidget` (no children, such as `RawImage`), `SingleChildRenderObjectWidget` (one `child`, such as `Padding` or `ColoredBox`) and `MultiChildRenderObjectWidget` (a `children` list, such as `Flex` and so `Column` and `Row`). Only the second kind appears in the render tree.

go deeper

for a junior

Recall that composing widgets build other widgets, while a smaller set of widgets actually create render objects.

for a middle

Name the ComponentElement subclasses and the three RenderObjectWidget base classes with examples, and explain why the render tree is smaller.

for a senior

Pick the right RenderObjectWidget base for a custom layout and explain how child render objects are attached through the nearest render object ancestor.

for a principal

Judge when the extra rendering-layer code is justified versus composing existing widgets, given the maintenance cost for the team.

## Two families of element Every element is one of two broad kinds, and the kind decides whether it shows up in the render tree. | | `ComponentElement` | `RenderObjectElement` | |---|---|---| | Created by | `StatelessWidget`, `StatefulWidget`, `ProxyWidget` (incl. `InheritedWidget`) | `RenderObjectWidget` subclasses | | Owns a render object | No | Yes, exactly one | | On mount | Builds its child widget and inflates it | Calls `createRenderObject`, attaches it, inflates children | | On update | Rebuilds, producing a new child widget | Calls `updateRenderObject`, then updates children | | Children | Always a single child element | Zero, one or many, depending on the widget kind | ## ComponentElement: composition A **`ComponentElement`** exists to turn one widget into another. Its `performRebuild` calls `build()` and hands the result to `updateChild`: - a **`StatelessElement`** calls `widget.build(this)`; - a **`StatefulElement`** calls `state.build(this)` on the `State` it owns; - a **`ProxyElement`** does not build anything new: it returns the `child` its widget was given. `InheritedElement` and `ParentDataElement` are proxies, which is why an `InheritedWidget` adds a node to the element tree but none to the render tree. Because these elements own no render object, when they need to put something on screen they delegate: their descendant eventually reaches a `RenderObjectWidget`, and that element attaches its render object to the nearest ancestor `RenderObjectElement`'s render object. ## RenderObjectElement: the bridge to layout and paint A **`RenderObjectElement`** keeps a render object in sync with its widget. It calls `createRenderObject(context)` once when mounted and `updateRenderObject(context, renderObject)` on each update, and it manages how child render objects are inserted, moved and removed as its child elements change. The framework provides three concrete element types, each paired with a widget base class: 1. **`LeafRenderObjectWidget`** → `LeafRenderObjectElement`: no child widgets. Examples: `RawImage`, `ErrorWidget`. 2. **`SingleChildRenderObjectWidget`** → `SingleChildRenderObjectElement`: one optional `child`. Examples: `Padding`, `SizedBox`, `ColoredBox`, `Opacity`, `Align`. 3. **`MultiChildRenderObjectWidget`** → `MultiChildRenderObjectElement`: a `children` list, reconciled in linear time per list. Examples: `Flex` (and so `Row` and `Column`), `Stack`, `Wrap`, `RichText`. The render object on the other side must support that child model: a single-child render object holds one child directly, while a multi-child one keeps a child list. ## Why the render tree is smaller Consider one crossword cell built as `Container(color: ..., padding: ..., child: Text(letter))`: - `Container` → `StatelessElement` (component, no render object) - it builds `ColoredBox` → `SingleChildRenderObjectElement` → a colored-box render object - inside that, `Padding` → single-child element → `RenderPadding` - `Text` → `StatelessElement` (component) - it normally builds `RichText` → `MultiChildRenderObjectElement` → `RenderParagraph` Five elements, three render objects. Across a board of hundreds of cells, layout and paint walk only the render objects, skipping every component node. ## How a child finds its render parent Because component elements have no render object, a render object element cannot simply attach to its element parent. When it mounts, `attachRenderObject` walks up to the **nearest ancestor `RenderObjectElement`** and asks it to insert the new render object at the given slot. That ancestor's element knows its child model: - a single-child element sets its render object's one child; - a multi-child element inserts the child into the list after the previous sibling; - when a child moves, its slot changes and the ancestor moves the render object rather than recreating it. `ParentDataWidget`s such as `Expanded` or `Positioned` are proxy elements too: they add no render object, but they write layout information into the child render object's `parentData` so that the `RenderFlex` or `RenderStack` above can use it. ## Picking a base class when you write one When you write a `RenderObjectWidget`, choose the base class by the number of children the render object needs: - no children (a custom gauge or a single drawn glyph): `LeafRenderObjectWidget`; - wraps one child (a custom clipping or offset box): `SingleChildRenderObjectWidget`; - lays out many children (a custom grid like a crossword board): `MultiChildRenderObjectWidget`. For children in named slots rather than a list, the framework has a slotted variant as well. Writing the render object itself (its layout, paint and hit testing) is a separate topic; the element kind only decides how children and the render object are wired together.

  • An InheritedWidget sits between a Padding and its child. Where does it appear in each tree?
    It has an element, an `InheritedElement`, which is a `ProxyElement` and therefore a `ComponentElement`. It owns no render object, so in the render tree the `RenderPadding`'s child is whatever render object the inherited widget's descendants produce.
  • Which base class would you pick for a custom widget that arranges a list of child widgets in a crossword layout?
    `MultiChildRenderObjectWidget`. It stores the `children` list, and its `MultiChildRenderObjectElement` reconciles child elements and inserts, moves and removes their render objects in your render object's child list. `Leaf` has no children and `SingleChild` holds exactly one.

saying these in an interview costs you the question

  • StatelessWidget creates a render object that paints its build output.
  • InheritedWidget adds a node to the render tree.
  • A SingleChildRenderObjectWidget can hold a list of children.
  • ComponentElement and RenderObjectElement are two names for the same class.
  • Every element has a render object, possibly an empty one.