Skip to content

Reconciler

Diffs successive virtual trees and applies the smallest set of native mutations that bring the on-screen view tree in line with the new description. The reconciler is platform-agnostic; it talks to native widgets exclusively through the NativeViewRegistry.

Virtual-tree reconciler with a batched, tag-based commit protocol.

Maintains a tree of VNode objects (each owning an integer tag that identifies its native view) and diffs incoming Element trees to compute the minimal set of native mutations.

The diff phase is pure: it never touches the native layer. Each pass appends ops (pythonnative.mutations) to a transaction list, and the commit applies them through a single backend.apply_mutations(ops) call. Event callbacks never cross into the native layer at all; they live in the Python-side EventRegistry, keyed by tag, so re-renders that only produce fresh closures cost nothing natively.

Supports:

  • Native elements (type is a string like "Text").
  • Function components (type is a callable decorated with component). Their hook state is preserved across renders.
  • Provider elements (type == "__Provider__"), which push and pop context values during tree traversal.
  • Error boundary elements (type == "__ErrorBoundary__"), which catch exceptions in child subtrees and render a fallback.
  • Key-based child reconciliation with indexed, move-aware inserts (keyed reorders emit one move per child instead of detach-all / re-attach-all).
  • Post-render effect flushing. After each commit, all queued effects are executed so they see the committed native tree.
  • Incremental layout: a parallel LayoutNode tree is cached across passes; clean subtrees keep their cached nodes (enabling the layout engine's measurement memo) and only frames that actually changed are sent to the native side.

Classes:

Name Description
VNode

A mounted Element plus its native identity.

Reconciler

Create, diff, and patch native view trees from Element descriptors.

Functions:

Name Description
next_tag

Allocate a fresh, process-unique view tag.

VNode

VNode(element: Element, children: List[VNode], tag: Optional[int] = None)

A mounted Element plus its native identity.

The reconciler walks parallel trees of VNode and incoming Element to compute the minimal set of native mutations.

Attributes:

Name Type Description
element

The Element last rendered into this slot.

tag

Integer identity of the underlying native view. Native elements own a fresh tag; transparent wrappers (function components, providers, error boundaries) delegate the tag of their rendered subtree root. None before the subtree renders anything.

native_view Any

The platform-native view object, resolved from the registry after commit. May be None for purely virtual wrappers that rendered nothing.

children

Ordered list of child VNode instances.

parent Optional[VNode]

The owning VNode, or None for the tree root. Used by local (component-scoped) re-renders to bubble a changed subtree root up to the nearest native container.

hook_state Any

The component's HookState when the node wraps a function component, otherwise None.

mounted bool

False once the node has been destroyed, so stale entries in the reconciler's dirty set are skipped.

Reconciler

Reconciler(backend: Any)

Create, diff, and patch native view trees from Element descriptors.

After each mount, reconcile, or flush_dirty pass the reconciler:

  1. applies the accumulated mutation ops in one batch,
  2. resolves freshly created native views and populates refs,
  3. flushes pending effects (so they see the committed tree), and
  4. runs the layout pass, emitting only changed frames.

Parameters:

Name Type Description Default
backend Any

An object implementing the registry protocol (apply_mutations, resolve_view, measure_intrinsic, command). PythonNative ships Android, iOS, and desktop registries; tests can pass a registry stocked with mock handlers.

required

Methods:

Name Description
mount

Build native views from element and return the root native view.

reconcile

Diff new_element against the current tree and patch native views.

root_view

Return the current root native view, or None before mount.

root_tag

Return the root native view's tag, or None before mount.

unmount

Destroy the entire mounted tree and release native views.

dispatch_command

Run an imperative command against the view registered under tag.

mark_dirty

Queue vnode (a function component) for a local re-render.

flush_dirty

Re-render only the component subtrees marked dirty since the last pass.

set_viewport_size

Update the viewport size and re-run layout if it changed.

compute_layout_for_test

Build and compute a layout tree without touching the backend.

mount

mount(element: Element) -> Any

Build native views from element and return the root native view.

Parameters:

Name Type Description Default
element Element

The root Element to render.

required

Returns:

Type Description
Any

The platform-native view that represents the root of the

Any

mounted tree.

reconcile

reconcile(new_element: Element) -> Any

Diff new_element against the current tree and patch native views.

Parameters:

Name Type Description Default
new_element Element

The desired root element after a state change.

required

Returns:

Type Description
Any

The (possibly replaced) root native view.

root_view

root_view() -> Any

Return the current root native view, or None before mount.

root_tag

root_tag() -> Optional[int]

Return the root native view's tag, or None before mount.

unmount

unmount() -> None

Destroy the entire mounted tree and release native views.

dispatch_command

dispatch_command(tag: Optional[int], name: str, args: Optional[Dict[str, Any]] = None) -> Any

Run an imperative command against the view registered under tag.

mark_dirty

mark_dirty(vnode: VNode) -> None

Queue vnode (a function component) for a local re-render.

Called by a component's use_state / use_reducer setter when its own state changes. The node is re-rendered on the next flush_dirty pass, which the screen host schedules. Marking is idempotent and cheap; the actual render is deferred so several setters (e.g. inside batch_updates) coalesce into a single pass.

flush_dirty

flush_dirty() -> Any

Re-render only the component subtrees marked dirty since the last pass.

This is the hot path for state-driven updates: instead of re-running the whole app from the root, each dirty function component re-runs its own body (reusing its HookState) and reconciles just its subtree. Nodes are processed shallowest-first so that when a dirty ancestor's re-render already covers a dirty descendant, the descendant is skipped (its _dirty flag is cleared by the ancestor pass). The whole batch commits as one native transaction.

Returns:

Type Description
Any

The (possibly replaced) root native view, so the host can

Any

re-attach it if the root changed.

set_viewport_size

set_viewport_size(width: float, height: float) -> None

Update the viewport size and re-run layout if it changed.

Called by the screen host whenever the platform reports a new container size (Android: onLayoutChange; iOS: viewDidLayoutSubviews). The first call after mount triggers the initial layout pass; subsequent identical sizes are no-ops.

Parameters:

Name Type Description Default
width float

Viewport width in points.

required
height float

Viewport height in points.

required

compute_layout_for_test

compute_layout_for_test(viewport_width: float, viewport_height: float) -> Optional[LayoutNode]

Build and compute a layout tree without touching the backend.

Test helper that returns the synthetic viewport LayoutNode with all descendants positioned. Returns None if no tree has been mounted yet.

next_tag

next_tag() -> int

Allocate a fresh, process-unique view tag.

Next steps