When you write a Flutter SingleChildRenderObjectWidget, what must createRenderObject and updateRenderObject each do, and what breaks if updateRenderObject is left empty?
answer
- create once, update many times
- copy every configurable field
- setters compare, then mark dirty
- empty update: stale rendering
- didUnmountRenderObject for cleanup
basics
~20 screateRenderObject builds the render object once, from every widget field and any context values. updateRenderObject must copy the same fields onto the existing object on every update; left empty, the UI keeps showing the first configuration after rebuilds.
solid answer
~40 sThe element calls `createRenderObject(context)` **once**, on mount, so it must construct the render object with every configurable value, including anything read from the context such as `Directionality.maybeOf(context)`. After that the element keeps the same render object and calls `updateRenderObject(context, renderObject)` on **every** update with the new widget. It must assign each of those values again, through setters that compare with the stored value and call `markNeedsLayout` or `markNeedsPaint` only when something changed. The base implementation is empty, so if you forget to override it, the first build looks right but later changes (a new colour, a new highlight) never reach the render object: the UI goes stale with no error. `didUnmountRenderObject` is the hook for releasing anything tied to the render object when the element is unmounted.
code
dart · 38 linesimport 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
class HighlightBox extends SingleChildRenderObjectWidget {
const HighlightBox({super.key, required this.color, super.child});
final Color color;
@override
RenderHighlightBox createRenderObject(BuildContext context) =>
RenderHighlightBox(color: color);
@override
void updateRenderObject(BuildContext context, RenderHighlightBox renderObject) {
// Without this line the box keeps its first colour forever.
renderObject.color = color;
}
}
class RenderHighlightBox extends RenderProxyBox {
RenderHighlightBox({required Color color}) : _color = color;
Color _color;
Color get color => _color;
set color(Color value) {
if (value == _color) {
return;
}
_color = value;
markNeedsPaint();
}
@override
void paint(PaintingContext context, Offset offset) {
context.canvas.drawRect(offset & size, Paint()..color = _color);
super.paint(context, offset);
}
}go deeper
Know that a render object widget creates its render object once and must update it afterwards.
Explain when the element calls createRenderObject, updateRenderObject and didUnmountRenderObject, and why the base update is empty.
Catch stale-render bugs by checking that both methods set the same fields, and write setters that compare and invalidate as narrowly as possible.
Decide when a custom render object widget is worth its maintenance burden, and set review rules such as mirrored create and update methods.
## The contract in one table A `RenderObjectWidget` describes a render object, but the element keeps the real one across rebuilds. Three widget methods connect them: | Method | Called | Must do | |---|---|---| | `createRenderObject(context)` | Once, when the element mounts | Build the render object from all widget fields and context values | | `updateRenderObject(context, renderObject)` | On every update with a new widget | Copy the same values onto the existing render object | | `didUnmountRenderObject(renderObject)` | Once, when the element unmounts | Release widget-side resources tied to that render object | The base `updateRenderObject` and `didUnmountRenderObject` are empty, so the framework will not complain if you leave them out. ## A crossword highlight box Suppose the crossword board wraps the active word's cells in a custom `HighlightBox` that paints a tinted background behind its child. It extends `SingleChildRenderObjectWidget` and has one field, `color`. - In `createRenderObject`, it returns a new render object configured with `color`. - In `updateRenderObject`, it sets `renderObject.color = color`. - The render object's `color` setter returns early if the value is equal, and otherwise stores it and calls `markNeedsPaint()`. When the player moves to another word, the board rebuilds, each `HighlightBox` gets a new widget with a new colour, the element calls `updateRenderObject`, and exactly the cells whose colour changed repaint. ## What goes wrong without `updateRenderObject` If `updateRenderObject` is not overridden: 1. The first frame is correct, because `createRenderObject` used the initial colour. 2. On rebuild, the element receives the new widget and calls the empty base method. 3. The render object keeps the old colour. Nothing is marked dirty, nothing repaints. The symptom is a UI that is right on first display and then ignores changes, with no exception and no warning. It is often misdiagnosed as a state-management bug, because the widget's fields are correct in the debugger; they simply never reach the render object. Hot restart hides it too, since that recreates everything with the latest values. The same class of bug appears in partial form: a field that `createRenderObject` passes but `updateRenderObject` forgets, or a context value such as the text direction that is read in one method but not the other. ## Debugging a suspected stale render object 1. In the DevTools Flutter inspector, select the widget and compare its **Widget properties** tab with its **Render object** tab. A custom render object shows its fields there only if it overrides `debugFillProperties`. If the widget says blue and the render object says red, the update path is broken. 2. Put a breakpoint in `updateRenderObject`. If it is never hit, the element is not being updated (the parent may be returning the identical widget instance). If it is hit, step into the setter. 3. In the setter, check that the equality comparison is correct and that it calls the right `markNeeds...` method. 4. Diff `createRenderObject` against `updateRenderObject` line by line; a missing field is the usual culprit. ## Writing the methods well - **Mirror the two methods.** Every value passed in `createRenderObject` should be assigned in `updateRenderObject`. Framework widgets such as `Padding` follow this exactly: both set `padding` and `textDirection`. - **Compare in the setters.** Setters on the render object should return early on equal values and call the narrowest invalidation: `markNeedsPaint` for a colour, `markNeedsLayout` for anything that affects size. - **Don't touch children.** Both methods are documented as not managing children; the element does that through the widget's `child` or `children`. - **Use the covariant parameter type.** Declaring `updateRenderObject(BuildContext context, RenderHighlightBox renderObject)` avoids casts. ## Picking the base class This contract is the same for all three kinds of render object widget. Choose `LeafRenderObjectWidget` when there are no children, `SingleChildRenderObjectWidget` for one `child`, and `MultiChildRenderObjectWidget` for a `children` list. How to implement the render object's own layout, paint and hit testing is a separate topic.
- Why should the render object's setter compare values instead of always calling markNeedsPaint?Because `updateRenderObject` runs on every rebuild, usually with unchanged values. Without the comparison, every rebuild of the board would schedule paint for every highlight box. Comparing first means rebuilds stay cheap and only changed values invalidate layout or paint.
- A widget reads Directionality.maybeOf(context) in createRenderObject but not in updateRenderObject. What bug results?If the ambient text direction changes, for example when the locale switches between left-to-right and right-to-left, the render object keeps the original direction. The widget rebuilds because it depends on `Directionality`, but the new value is never assigned, so layout stays mirrored the old way.
saying these in an interview costs you the question
- createRenderObject runs on every rebuild, so updateRenderObject is optional.
- The framework copies widget fields to the render object automatically.
- updateRenderObject should also insert and remove child render objects.
- Setters should always call markNeedsLayout to be safe.
- A stale custom render object must be a state-management bug.