CollectionView Overview
CollectionView
CollectionView is Hosanna's large-list surface. It virtualizes rows and cells, recycles view instances, and keeps focus state stable while data changes. Use it for rails, grids, tabbed rows, EPG-style rows, and any list that can grow or load dynamically.
Choosing Between Repeater, ScrollView, and CollectionView
| Content | Use |
|---|---|
| A few similar buttons, icons, or badges | Repeater |
| One heterogeneous detail page, settings page, or form that extends beyond the viewport | ScrollView |
| A data-driven list, rail, grid, EPG, season list, episode list, or collection that can grow | CollectionView |
CollectionView is also a strong choice for detail screens and form-like experiences when their sections can be represented as rows or items. It provides better scaling and can use AppConfig fragments for reusable presentation and separately managed layout and style updates.
Do not embed Repeater, ScrollView, or ScrollContainer inside CollectionView cells, custom cells, or supplementary/header views. Use CollectionView rows, cells, headers, and fragments so the collection remains the single owner of focus, scrolling, recycling, and updates.
A CollectionView renders visible rows, each row positions its cells, and focus is tracked by row and item index.
Quick Start
Collections Collectionview Basic
@view('CollectionsCollectionviewBasic')
export class CollectionsCollectionviewBasicView extends BaseExampleScreenView<CollectionsCollectionviewBasicState> {
@state private dataSource = new CollectionViewDataSource([]);
constructor() {
super();
// Seed a couple of rows using the built-in 'rows.regular' style
const rows: ICollectionViewDataSourceRow[] = [
this.createRow('Row A', 6),
this.createRow('Row B', 8),
];
this.dataSource.appendRows(rows, undefined, true);
}
protected getViews(): ViewStruct<ViewState>[] {
// --- edit below ---
return [
VGroup([
Label({ text: 'CollectionView: Basic' }),
Button({ text: 'Add Row', width: 300 })
.isInitialFocus(true)
.onClick(() => this.dataSource.appendRows([this.createRow('New Row', 5)], undefined, true)),
CollectionView({ id: 'cv' })
.translation([0, 80])
.dataSource(this.dataSource),
]).itemSpacing(12)
];
// --- edit above ---
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private createRow(label: string, count = 6): ICollectionViewDataSourceRow<any, any> {
const items = Array(count).fill(0).map((_, i) => ({
id: `${label}-item-${i + 1}`,
imageSet: { $RES$: 'https://via.assets.so/movie.png?w=384&h=216&q=85' },
}));
return {
id: `row-${label}-${Math.random().toString(36).slice(2, 7)}`,
settingsKey: 'rows.regular',
data: { label },
items,
};
}
}Core Model
- View:
CollectionView({ id }).dataSource(dataSource)renders the currentCollectionViewDataSource. - Rows: each data source row has an
id,settingsKey, optionalsettingsOverrides, optional rowdata, and anitemsarray. - Settings: row and cell styles live in
assets/meta/app.config.jsonunder top-levelrowsandcells. - Cells: most rows use
DynamicCell, which renders fragment JSON and appliesnormal,focused,selected, anddisabledstate styles. - Focus:
focusedIndicesstores[rowIndex, itemIndex]; events expose row and item indices, and helper methods return focused row/item data.
AppConfig Row and Cell Styles
Rows are configured by style keys such as rows.regular. settingsOverrides can adjust an individual row without creating a new global style.
{
"rows": {
"regular": {
"rowType": "HorizontalRow",
"height": 317,
"contentOffset": [255, 0],
"cellSize": [384, 216],
"spacing": 15,
"screenPosition": [0, 150],
"cellSettingsKey": "cells.regular",
"headerSettings": {
"height": 51,
"labelOffset": [255, 0],
"fontKey": "~theme.fonts.heading-24"
},
"focusSettings": {
"indicatorImageUri": "images/input-focusrect.9.png",
"indicatorAppearance": "onTop"
}
}
}
}
Cells use fragment-style config. $supportsDataMap enables ${data.*} bindings; $RES$ inside a data path is replaced with the active resolution suffix.
{
"cells": {
"regular": {
"$supportsDataMap": true,
"views": {
"base": [
{
"id": "poster",
"subType": "Poster",
"width": 384,
"height": 216,
"uri": "${data.imageSet.$RES$}"
}
],
"normal": {
"poster": { "loadingBitmapUri": "~theme.images.placeholder" }
},
"focused": {
"poster": { "scale": [1.05, 1.05] }
}
}
}
}
}
API Surface
- CollectionView:
dataSource,rowSpacing,focusIndicatorAppearance,onItemSelected,onItemLongPressSelected,onOptionsKeyPressed,onItemFocus,onRowFocus. - Navigation helpers:
jumpToRow(rowIndex, itemIndex?, force?),scrollToRow(rowIndex, itemIndex?, force?),renderForIndices(rowIndex, itemIndex). - Focused data helpers:
getFocusedDataSourceRow(),getFocusedDataSourceItem(),getDataSourceItemForIndex(rowIndex, itemIndex),getCellForIndex(rowIndex, itemIndex). - DataSource:
appendRows,replaceAllRows,appendItemsToRow,removeItemsFromRow,updateItem,removeRow,moveRowToIndex,changeRowEnabled,ChangeRowHidden,applyUpdates. - Pointer support: mouse hover/click, wheel scrolling, touch drag, horizontal row axis locking, momentum, and pointer focus indicator modes are handled by the web input adapters.
Edge-to-Edge Collections
CollectionViewSafeAreaBehavior.FullViewportSafeFocus keeps the collection's drawable viewport edge-to-edge while using native interaction insets for focus positions and the first/last scroll extents. It merges those native values with explicit scrollInsets; existing collections default to None and retain their current geometry.
CollectionView({
id: 'fullViewportFeed',
width: screenWidth,
height: screenHeight,
viewportHeight: screenHeight,
floatingFocusAreaHeight: screenHeight,
safeAreaBehavior: CollectionViewSafeAreaBehavior.FullViewportSafeFocus,
dataSource,
});
See Safe Areas and Edge-to-Edge Screens for the full screen-policy model and native verification guidance.