Skip to content

Kanban Boards — cdx_kanban_view

cdx_kanban_view is the CDX kanban package. Use it when an entity list needs to be grouped into operational columns such as "Queued", "In Review", "Revision", and "Done".

The package composes CDX UI styling, but it is imported directly:

dart
import 'package:cdx_kanban_view/cdx_kanban_view.dart';

Live Example

Open the full route: Kanban View.

When To Use It

Use a kanban board when the primary question is where is this item in the flow? Each column is a server-side filter, so a column can represent one value, several values, or a richer cdx_query expression.

Good fits:

ScenarioWhy kanban fits
Workflow tasksShow user tasks by lifecycle state or assignment stage
ActivitiesGroup records by execution/review/revision state
Equipment or documentsTrack operational readiness or approval state
Entity dashboardsOffer an alternate layout beside the default table

Model

Kanban data flowKanbanQuery feeds a data source, the data source feeds a controller, and the controller drives a board with columns and item cards.KanbanQuerycolumns, filters,search, orderDataSourcefetchColumns +fetchColumn pageControllerMobX state,pages, realtimeKanbanBoard -> columns -> cards
text
KanbanQuery
  -> KanbanDataSource<T>
  -> KanbanController<T>
  -> KanbanBoard<T>

KanbanQuery declares the shared base filter, sorting, search, columns, and optional per-column segments. KanbanDataSource<T> executes those queries against your backend. KanbanController<T> owns raw MobX state, pagination, refreshes, and realtime reconciliation. KanbanBoard<T> renders the result.

Query And Columns

dart
final query = KanbanQuery(
  baseFilter: myActivitiesFilter,
  orderBy: const [
    OrderExpr(aliasOrField: 'updated_at', direction: SortDirection.desc),
  ],
  columns: [
    KanbanColumnSpec(
      key: 'in_review',
      title: 'In Review',
      accent: Colors.amber,
      filter: statusIn(['submitted', 'under_review', 'revision_requested']),
      segments: [
        KanbanSegment(
          key: 'submitted',
          label: 'Submitted',
          filter: statusIn(['submitted', 'under_review']),
        ),
        KanbanSegment(
          key: 'revision',
          label: 'Revision',
          filter: statusIn(['revision_requested']),
        ),
      ],
    ),
  ],
);

The effective query is:

text
base filter AND column filter AND active segment filter

The board does not evaluate filters locally. Filtering remains server-side through cdx_query.

Data Source

dart
class ActivityKanbanDataSource extends KanbanDataSource<Activity> {
  ActivityKanbanDataSource(super.query, this.api);

  final ActivityApi api;

  @override
  Future<List<KanbanColumnData<Activity>>> fetchColumns({
    int limit = 10,
    int offset = 0,
  }) {
    return api.fetchKanbanColumns(
      query: query,
      limit: limit,
      offset: offset,
    );
  }

  @override
  Future<KanbanColumnPage<Activity>> fetchColumn(
    String key, {
    int limit = 10,
    int offset = 0,
    String? segmentKey,
    Filter? extraFilter,
  }) {
    return api.fetchKanbanColumn(
      filter: query.columnFilter(key, segmentKey: segmentKey),
      extraFilter: extraFilter,
      limit: limit,
      offset: offset,
    );
  }
}

Each column result carries a column-global total. That lets the header count and the per-column pager remain honest even when only one page of items is loaded.

API Parameters

TypeParameterPurpose
KanbanQuerycolumnsOrdered board columns. Required.
KanbanQuerybaseFilterPredicate applied by the data source to every column.
KanbanQuerysearchOptional free-text search applied by the data source.
KanbanQueryorderByShared ordering inside every column.
KanbanColumnSpeckey, titleStable column id and header label.
KanbanColumnSpecfilterServer-side predicate for the column.
KanbanColumnSpecsegmentsOptional per-column narrowing filters; "All" is synthetic.
KanbanColumnSpecisPlaceholderRenders a ghost slot without fetching data.
KanbanDataSource.fetchColumnextraFilterUser-selected per-column filter; implementations must AND it with base, column, and segment filters.
KanbanControlleridOf, columnOfStable identity and realtime placement.
KanbanBoardonFilterRequestedOptional hook that shows a host-owned filter picker and applies its returned filter.

Rendering

dart
final controller = KanbanController<Activity>(
  dataSource: ActivityKanbanDataSource(query, api),
  idOf: (activity) => activity.id,
  columnOf: (activity) => columnKeyForStatus(activity.status),
  pageSize: 10,
);

await controller.load();

KanbanBoard<Activity>(
  controller: controller,
  itemBuilder: (context, activity) => KanbanItemView(
    item: KanbanItem(
      id: activity.id,
      title: activity.name,
      subtitle: activity.statusLabel,
    ),
  ),
  onItemTap: (activity) => openActivity(activity.id),
);

Return KanbanItemView for the default card, or provide a fully custom widget from itemBuilder.

Pagination, Refresh, Realtime

Pagination is per column:

dart
controller.nextPage('in_review');
controller.prevPage('in_review');
controller.goToPage('in_review', 2);
controller.pageCount('in_review');

Refresh and realtime updates are granular:

dart
controller.refreshColumn('in_review');
controller.refreshColumns(['todo', 'done']);
controller.refresh();

controller.applyUpsert(changedActivity);
controller.applyRemoval(removedId);

applyUpsert uses idOf and columnOf to move an item between columns and keep counts in sync without forcing a full board reload.

Customization

dart
KanbanBoard<Activity>(
  controller: controller,
  itemBuilder: buildActivityCard,
  theme: const KanbanTheme(
    columnMinWidth: 320,
    itemSpacing: 8,
  ),
  headerBuilder: (context, column, state) => MyHeader(column, state),
  footerBuilder: (context, column, state) => MyFooter(column, state),
  emptyBuilder: (context, column) => EmptyState(title: 'No items'),
  errorBuilder: (context, column, error, stackTrace) => ErrorState(error: error),
);

Use columnBuilder only when the whole column chrome needs replacing. Prefer slot builders when you want CDX defaults plus custom header, footer, empty, loading, or error states.

Entity-System Relationship

Entity UI can expose a kanban layout through generated or hand-written EntityKanbanConfig<T>. The kanban package itself stays generic: it does not depend on vyuh_entity_system_ui, and entity-system code adapts its list APIs into KanbanDataSource<T>.

  • Data Tables — use when comparison and sorting matter more than state grouping
  • Tree Views — pair hierarchy context with any detail surface
  • Search and Filters — shared cdx_query filter vocabulary