Views, SubViews, and Children
Why This Matters
Hosanna uses a declarative view tree. You describe state and return view structs; the framework builds real views, wires parent/child relationships, and mounts native renderers.
Key Concepts
- View: A virtual object with state that renders through a platform renderer (Roku SceneGraph, web DOM shim). Backed by
BaseView<T>. - SubViews: Views created by a composite view’s
getViews()result. They are declared children and tracked insubViews(flat list) andsubViewsById. - Children: The on-screen mounted hierarchy. Managed via
addChild/insertChild/removeChildand tracked inchildrenandchildrenById.
SubViews are the declared structure from getViews(). Children are the mounted, on-screen structure. Most composite views have both.
How Views Are Built
- You update state and return structs from
getViews(). - Hosanna inflates structs into views via
buildView(). - Each view
onMount()creates a renderer and becomes a child of its parent. - IDs are resolved and stored for fast lookup.
Sequence
IDs and Auto-Generation
- You can set
idon any struct. If omitted, Hosanna auto-generates hierarchical IDs based on position. - IDs are stable within a render pass and used for reconciliation and lookups.
Assign explicit IDs to views you need to access later (focus, bounds, animations). Let others auto-generate.
How IDs Are Applied
- During
applyStateChanges, Hosanna callsapplyIdsToViewStructs()to ensure each struct has a unique ID. - Auto IDs follow a parent-index pattern like
__auto-0-2when not specified.
Working with SubViews and Children
getSubView<T>(id, deep?)looks up declared subViews (optionally recursive).getChildView<T>(id)returns mounted children by ID.getSubViewRenderer<T>(id)returns a subView’s renderer if mounted.
Use getSubView for declaration-time relationships and getChildView when you need the mounted instance.
Generics and Safety
APIs accept generics so you get type-safe results:
const label = this.getSubView<Label>('title');
const grid = this.getChildView<GridGroup>('results');
const renderer = this.getSubViewRenderer<ISGNGroup>('thumbnail');
Lookups can return undefined. Guard before use, especially during initial mount.
Class Relationships
Getting Views and Renderers
- Views expose
renderer?: R. It is present when mounted. - Use
addSubView/insertSubView/removeSubViewto manage declared subViews. - Use
addChild/insertChild/removeChildto manage mounted hierarchy. viewOwnerlinks a subView back to its creator.
onMount() calls createRenderer() and attaches it to the parent’s renderer. On web, renderers are fake SG nodes; on Roku they are SceneGraph nodes.
Focus and Layout Tips
- Composite views act as focus groups and can set an initial focus via
isInitialFocuson a child struct. layoutChildren()positions mounted children using theirtranslation,width, andheight.- Use
getBoundsInParent(view)orgetBoundsInApp()when you need math-friendly rectangles.
Use setFocusedSubview(id) to hint where focus should land when children change.
Troubleshooting
- If a lookup fails, verify the ID is actually present on the struct in the latest
getViews()output. - Ensure the view is mounted before touching its
renderer. - Prefer explicit IDs on interactive views to avoid auto-ID shifts when order changes.
⚙️ Key APIs
getSubView<T>(id, deep?): Find a declared subView by ID; deep search optional.getChildView<T>(id): Find a mounted child by ID.getSubViewRenderer<T>(id): Get a subView’s renderer.addChild/insertChild/removeChild: Manage mounted hierarchy.addSubView/insertSubView/removeSubView: Manage declared subViews.
Don’t change state inside getViews(). Compute structure only; set state before/after.