Tree Views — cdx_tree_view
cdx_tree_view is the CDX hierarchy package. Use it when users need to choose a parent, folder, area, collection, or other container before working with a detail surface.
The package wraps Flutter's two-dimensional tree renderer but keeps the public API in CDX terms:
import 'package:cdx_tree_view/cdx_tree_view.dart';Live Example
Open the full route: Tree View.
When To Use It
Use a tree when the primary question is where does this item belong? The tree should usually provide context, not replace the detail view.
Good fits:
| Scenario | Pattern |
|---|---|
| Areas and equipment | Select an area, show equipment or activities on the right |
| Folders and documents | Select a folder, show documents or metadata on the right |
| Product libraries | Select a category, show SKUs or forms on the right |
| Entity management | Use hierarchy selection as a base filter for a normal collection |
Model
CdxTreeDataSource<T>
-> CdxTreeController<T>
-> CdxTreeView<T>
-> CdxTreeSplitView<T>CdxTreeDataSource<T> loads roots and children. CdxTreeController<T> owns raw MobX state, expansion, selection, loading, and errors. CdxTreeView<T> renders the hierarchy. CdxTreeSplitView<T> pairs the tree with any detail builder.
Data Source
class FolderTreeDataSource extends CdxTreeDataSource<Folder> {
FolderTreeDataSource(this.api);
final FolderApi api;
@override
Future<List<CdxTreeItem<Folder>>> loadRoots() async {
final folders = await api.childrenOf(null);
return [
for (final folder in folders)
CdxTreeItem(
id: folder.id,
value: folder,
title: folder.name,
hasChildren: folder.hasChildren,
),
];
}
@override
Future<List<CdxTreeItem<Folder>>> loadChildren(
CdxTreeItem<Folder> parent,
) async {
final folders = await api.childrenOf(parent.id);
return [
for (final folder in folders)
CdxTreeItem(
id: folder.id,
value: folder,
title: folder.name,
hasChildren: folder.hasChildren,
),
];
}
}Split View
final controller = CdxTreeController<Folder>(
dataSource: FolderTreeDataSource(api),
eagerDepth: 1,
);
CdxTreeSplitView<Folder>(
controller: controller,
detailBuilder: (context, selected) {
if (selected == null) {
return const EmptyState(title: 'Select a folder');
}
return DocumentsTable(folderId: selected.value.id);
},
);The selected item can drive a table, kanban board, form, read-only detail, or any other widget.
Controller API
await controller.load(selectFirst: true);
await controller.ensureChildrenLoaded(node, expandAfterLoad: true);
await controller.toggleNode(node);
await controller.reloadNode(node);
controller.select(item);
controller.clearSelection();Use eagerDepth for shallow hierarchies where the first few levels should be loaded immediately. Leave it at the lazy default when a hierarchy can be large.
API Parameters
| Type | Parameter | Purpose |
|---|---|---|
CdxTreeDataSource | loadRoots() | Load top-level containers. |
CdxTreeDataSource | loadChildren(parent) | Load child containers for a node. |
CdxTreeController | eagerDepth | Prefetch descendants after each load; 0 means roots only. |
CdxTreeController.load | selectFirst | Select the first root after loading. |
CdxTreeView | autoLoad, selectFirstOnLoad | Widget-level initial load behavior. |
CdxTreeView | rowExtent, indent, nodeBuilder | Row sizing and custom row rendering. |
CdxTreeSplitView | treeWidth, minTreeWidth, maxTreeWidth | Resizable rail constraints. |
CdxTreeSplitView | detailBuilder | Host-owned detail surface for controller.selectedItem. |
Entity-System Pattern
In entity-system UI, treat the tree as a context selector. The selected node contributes a base filter to the right-hand surface:
selected tree node filter
AND detail table or kanban filter
AND detail search and sortDo not let table filters prune the tree. The hierarchy should remain stable while the detail surface behaves like a normal entity collection.
Cross-links
- Data Tables — common right-hand detail surface
- Kanban Boards — alternate right-hand grouped surface
- Master Detail — entity UI composition pattern