In Flutter, what happens to an Element during mount, update, deactivate and unmount, and when does each step occur?
answer
- initial, active, inactive, defunct
- inflateWidget then mount(parent, slot)
- update swaps in the new widget
- inactive list until end of frame
- unmount disposes the render object
basics
~20 sAn element is created and mounted when its widget first appears, updated in place when a compatible new widget arrives, deactivated into an inactive list when removed, and unmounted at the end of the frame unless something reclaims it.
solid answer
~40 sWhen a parent first needs a child, `inflateWidget` calls `widget.createElement()` and `mount(parent, slot)`: the element becomes **active**, records parent, slot and depth, and either builds its child (a `ComponentElement`) or calls `createRenderObject` and attaches it (a `RenderObjectElement`). On later rebuilds, a new widget with the same runtime type and key goes to `update(newWidget)`, which keeps the element and pushes the new configuration down. When the widget disappears, `deactivateChild` detaches its render object and puts it on the owner's **inactive** list, removing its inherited dependencies. At the end of the frame, `finalizeTree` **unmounts** anything still inactive, making it **defunct**: a `State` gets `dispose`, and a render object element disposes its render object. An inactive element can be reactivated before then, which is how a `GlobalKey` moves a subtree.
go deeper
Recall the order: mount when a widget first appears, update when a compatible widget replaces it, unmount when it is removed for good.
Explain inflateWidget, the inactive list and finalizeTree, and what ComponentElement and RenderObjectElement each do when mounted and updated.
Use the lifecycle to explain lost or preserved State, GlobalKey reparenting within a frame, and why a stored context can point at a defunct element.
Relate element identity to architecture choices, such as where to keep long-lived objects so that a subtree swap does not silently reset them.
## The four lifecycle states Every `Element` moves through a private lifecycle enum with four values: | State | Meaning | |---|---| | `initial` | Created by `createElement()`, not yet in the tree | | `active` | Mounted (or reactivated) and part of the tree | | `inactive` | Removed from its parent, waiting on the owner's inactive list | | `defunct` | Unmounted; must never be used again | ## Mount: joining the tree When a parent's `updateChild` finds no reusable element, it calls **`inflateWidget(newWidget, slot)`**. That calls `newWidget.createElement()` and then **`mount(parent, newSlot)`** on the new element. `Element.mount`: - stores the parent and the **slot**, the position information the parent uses to place this child; - sets the lifecycle to `active` and computes the element's **depth**; - registers a `GlobalKey` if the widget has one; - takes over the parent's inherited-widget lookup table. Subclasses then do their own work: 1. A **`ComponentElement`** (stateless, stateful, inherited) performs its first build, calling `build` and inflating whatever widget comes back as its single child. A `StatefulElement` has already called `createState()` in its constructor, and runs the `State`'s initial callbacks before that first build. 2. A **`RenderObjectElement`** calls `widget.createRenderObject(this)`, attaches the render object into the render tree at the ancestor render object and slot, and inflates its child widgets, if any. ## Update: a new configuration for the same element On a later rebuild, if the parent returns a widget with the same runtime type and key at that position, `updateChild` calls **`update(newWidget)`**. The element swaps its widget reference and then: - a `StatelessElement` or `StatefulElement` rebuilds (a stateful one also calls `didUpdateWidget` on its `State` with the old widget first); - a `RenderObjectElement` calls `widget.updateRenderObject(this, renderObject)` and then updates its children. If the parent returns the identical widget instance, `update` is not called at all. ## Deactivate: removed, but not yet gone When the new widget is `null`, or cannot update the old element, the parent calls **`deactivateChild(child)`**. That: 1. clears the child's parent pointer; 2. **detaches** the subtree's render objects from the render tree; 3. adds the element to the `BuildOwner`'s **inactive elements** list, which deactivates the subtree and unregisters it from every `InheritedElement` it depended on. The element is now `inactive`. It still has its `State` and render objects, and can be **reactivated** during the same frame: a widget with the same `GlobalKey` appearing elsewhere lets the framework pull the element out of the inactive list, call `activate`, and reattach it under the new parent, preserving the whole subtree. ## Unmount: the end of the line At the end of the frame, after build, layout and paint, `WidgetsBinding.drawFrame` calls **`BuildOwner.finalizeTree()`**, which unmounts everything still on the inactive list. `unmount`: - unregisters its `GlobalKey`; - drops references to its widget and dependencies and becomes `defunct`; - for a `StatefulElement`, calls `State.dispose`; - for a `RenderObjectElement`, calls the widget's `didUnmountRenderObject` and then **disposes** the render object. ## A clue panel, traced The crossword screen shows a clue panel only while a word is selected: 1. **Select a word.** The panel's widget appears; `inflateWidget` creates its element and calls `mount`, and the panel's `State` is created. 2. **Select another word.** The panel widget has the same type at the same position, so its element gets `update` and keeps its `State`, for example its scroll offset. 3. **Clear the selection.** The parent stops including the panel widget, so the old element is deactivated: `deactivateChild` detaches the panel's render objects and puts its element on the inactive list. 4. **End of that frame.** Nothing reclaimed it, so `finalizeTree` unmounts it; its `State` is disposed and its render objects are disposed. 5. **Select a word again later.** A brand-new element and `State` are created: anything the old panel remembered is gone. ## Why the lifecycle matters in practice - A `State` survives exactly as long as its element does: an `update` keeps it, an inflate-after-deactivate replaces it. - The inactive window is why removing and re-adding a globally keyed subtree in one frame does not rebuild it from scratch. - Holding a reference to a `BuildContext` after its element is unmounted means holding a defunct element; using it fails. The detailed `State` callbacks, the key-matching rule and using a context after an `await` each have their own topic; this one is the element's side of the story.
- Why does Flutter park removed elements in an inactive list instead of unmounting them immediately?Because a subtree with a `GlobalKey` may reappear elsewhere in the same frame. Keeping the element inactive until `finalizeTree` runs lets the framework reactivate it under its new parent with its `State` and render objects intact, instead of disposing and rebuilding everything.
- What does a RenderObjectElement do with its render object on unmount?It calls the widget's `didUnmountRenderObject(renderObject)` so the widget can release anything tied to it, then calls `dispose()` on the render object and drops its reference. The render object was already detached from the render tree when the element was deactivated.
saying these in an interview costs you the question
- A removed element is disposed immediately, in the middle of the build.
- update creates a new element with the new widget.
- Deactivated elements keep receiving inherited-widget notifications.
- A defunct element can be mounted again later.
- createRenderObject is called on every update of the element.