Fragment Performance and Debugging
Fragments are designed for reuse. CollectionView can create, release, and reacquire the same fragment SG tree many times while scrolling. Correct fragment code therefore overwrites all item-specific and status-specific state on every reuse.
Prefer declarative work
Choose the narrowest mechanism that expresses the update:
| Need | Prefer |
|---|---|
| Copy one item value to one SG field | ${data.path} |
| Derive one field from item data and literal arguments | ${fn.name(...)} |
| Derive primitive fields from data, size, focus, or device inputs | computed.name |
| Relate child geometry | Fragment constraints |
| Measure nodes, coordinate several updates, or manage resources | Fragment callback |
Callbacks are an escape hatch, not the default layout system. Imperative node changes are mutable pooled state and must be restored before the fragment is shown for another item.
Make pooled reuse deterministic
- Put the complete first-render appearance in
views.base. - In
views.normal, restore every field changed by another status. - Bind every item-specific field or reset it in
onDataChange. - Release observers, timers, media, and other resources in
onUnmount. - Give every addressable descendant a unique child ID.
- Omit the fragment root
idfor repeated and prefetched cell styles so the provider can generate unique instance IDs. - Do not depend on fragment creation order or generated IDs.
Released fragments retain their SG nodes. A field changed directly by a callback remains changed until a later binding, status overlay, callback, or pristine node-pool reset writes it again.
Constraint layout caching
Set cacheFragmentConstraintLayout: true only when the fragment's constraint
writes are stable for the runtime cache inputs.
The runtime automatically splits rules:
- rules without computed arguments use a geometry-oriented cache key
- rules with computed arguments use a full key that includes computed values
Raw item data is not independently included in a layout key. Status affects a
key only when the computed/constraint dependency graph references focus.
Caching is therefore unsafe when callbacks, measured data-bound content, or
status overlays change geometry outside those tracked inputs.
cacheFragmentConstraintLayoutKeyMode remains in the public style type, but
the current DynamicCell and FragmentView hosts do not consult it when
choosing their runtime keys. Do not rely on "full" or "geometry" changing
host behavior.
AppConfig invalidation clears the shared constraint-layout cache. A pooled fragment created under an older style or locale revision is discarded when it is released or reacquired; an in-use SG tree is not rebuilt in place.
Content-driven height
contentHeightAnchorViewId runs the fragment constraints against a tall probe
height, measures the named child's bottom edge, applies that value to the host,
and then runs constraints again.
Use it when one child deterministically represents the content bottom. On a
standalone FragmentView, the host height is updated. In CollectionView, the
cell's local size can change, but this option does not promise to recalculate
the containing row's layout or height.
Diagnose common failures
| Symptom | Check |
|---|---|
Literal ${data.title} appears | $supportsDataMap is true; the binding is an exact views.base field; the child has an ID. |
| Bound field becomes empty | The data path exists and resolves to the expected SG field type. Missing paths write undefined. |
${fn.*} does nothing | Register the named onSpecificViewDataChange handler before the fragment is first acquired. |
| Status gets stuck after focus leaves | views.normal does not restore every field changed by focused or another overlay. |
First cell ignores views.normal | Move the required initial values into views.base; a fresh DynamicCell may already be normal and skip that overlay. |
| Constraint target cannot be found | IDs are unique, the target exists, and child-to-child references share a parent. |
| Constraint produces a negative size | Check anchor order and margins; dimensions are not implicitly clamped without min or max. |
| Recycled item shows prior state | A callback or status overlay mutated a field that data binding and normal-state restoration do not reset. |
| Unknown style renders fallback content | Inspect cells.missingCell; without that fallback, missing style acquisition fails. |
| Shape is missing on Roku | Ensure loadShapes() completed and the shape has valid reference dimensions where required. |
Inline constraint syntax is validated while AppConfig resolves the style. Missing child references and other explicit-rule problems can surface later when runtime constraint evaluation resolves SG nodes.
Provider diagnostics
ViewFragmentProvider.getFragmentCounts() reports total fragments plus
available and created counts by style key. Use it to spot unexpected style
churn or a pool that never receives released instances.
ViewFragmentProvider.getDanglingFragmentsForDebug() returns every fragment
currently checked out of the available pool. Each row includes the fragment
root id, styleKey, parent id and subtype, the renderer _hid when
available, and parentIsHolder. Check isDangling first:
truewithdanglingReason: "Fragment view has no SG parent"means the provider considers the fragment in use but its root is detached.truewith a fragment-holder reason means acquisition occurred but the root was never attached to its intended row or host.falsemeans the fragment is checked out and attached; it is not evidence of a leak by itself.
Compare these rows with getFragmentCounts() after the owning screen,
CollectionView, or FragmentView unmounts. A surviving checked-out row then
points to an unmatched host cleanup or release path.
The provider also reports:
- duplicate child IDs; later matches win lookup
- callback names that were not registered; regular callbacks become no-ops
- missing styles and
cells.missingCellfallback use - unresolved or unavailable compiled shapes
Callback exceptions are not isolated by the provider. Treat callback handlers as application code: validate inputs, keep them synchronous and bounded, and clean up symmetrically.
Current inheritance limitation
Do not rely on fragment $extends to inherit ${data.*}, ${fn.*}, or
base-field computed.name mappings. Resolving the base removes those authored
strings into its internal map, while resolving the derived fragment rebuilds
that map and preserves only constraints and computed declarations.
Until the framework behavior changes, repeat required bindings in the derived
fragment or avoid $extends for data-bound fragment trees. Callback groups and
ordinary static/status fields still follow their documented merge behavior.
Sample map
The hosanna-ui-samples-public repository contains focused rigs:
| Capability | Source |
|---|---|
| Standalone updates, callbacks, status, and variable width | src/hosanna-ui-examples/rigs/views/FragmentViewRig.ts |
Targeted updateData() | src/hosanna-ui-examples/rigs/views/BaseViewFragmentRig.ts |
| Constraint fixtures | src/hosanna-ui-examples/rigs/views/ViewFragmentConstraintFixtures.ts |
| Inline constraints | src/hosanna-ui-examples/rigs/collection-view/CollectionViewInlineConstraintsRig.ts |
| Computed fixtures | src/hosanna-ui-examples/rigs/views/ViewFragmentComputedFixtures.ts |
| Responsive computed CollectionView cells | src/hosanna-ui-examples/rigs/collection-view/CollectionViewFragmentComputedVariablesRig.ts |
| Status combinations | src/hosanna-ui-examples/rigs/collection-view/CellSelectedStatusRig.ts |
| Pool cleanup and mutable-state reset | src/hosanna-ui-examples/rigs/wow-samples/LumenMaskedWorldsCallbacks.ts |
| Resource and observer cleanup | src/hosanna-ui-examples/rigs/wow-samples/LumenVideoCellCallbacks.ts |
| Missing style behavior | src/hosanna-ui-examples/rigs/styles/MissingFragmentStyleRig.ts |
Use the rigs as implementation evidence, but copy the declarative binding/computed/constraint patterns before adopting callback-driven geometry.