Tinyrack

VirtualList

Render large variable-size linear collections lazily while preserving a stable visible anchor.

Render a large variable-size linear collection lazily while preserving a stable visible item as data and item sizes change.

Contract

AxisContract
IdentityitemKey must return a unique, stable key. Keys preserve item state and define the anchor across insertions, removals, reordering, and size changes.
MeasurementestimatedItemExtent seeds unmeasured items. Natural layout replaces each estimate, so total extent and the scrollbar thumb are approximate until measurement completes.
Followingfollow stays pinned only while the reader is already at that logical edge. Scrolling away preserves the visible anchor; returning to the edge enables following again. Call holdVisibleAnchorForNextLayout() before a disclosure resize that must override following once.
LoadingLeading and trailing edge requests signal the consumer once per requestKey and can render a measured status slot. The consumer owns fetching, errors, retry attempts, cursors, and completion.
RestorationPass an opaque initialSnapshot or set pageStorageId. A compatible snapshot wins over initialPosition; missing keys fall back to the initial position.
AccessOnly the visible and cached range exists in the widget tree. Offscreen local state, full-document search, print, selection, and assistive virtual-cursor access are consumer concerns; use non-virtual rendering when the whole document must remain available.

The list owns one bounded viewport and keeps data in forward order. It preserves one surviving visible item wrapper at the same viewport-relative coordinate. If a mutation crosses the visible range, every visible item cannot remain fixed at once.

Install

Add the package, then import its public library.

flutter pub add tinyrack_ui
import 'package:tinyrack_ui/tinyrack_ui.dart';

Playground

Usage

import 'package:material_ui/material_ui.dart';
import 'package:tinyrack_ui/tinyrack_ui.dart';

TRVirtualList<Message, String>(
  items: messages,
  itemKey: (message) => message.id,
  estimatedItemExtent: (message, index) => TRMeasurements.measureSm,
  initialPosition: const TRVirtualListInitialPosition.trailing(),
  follow: TRVirtualListFollow.trailing,
  itemBuilder: (context, message, index) => MessageRow(message),
)

Examples

Load older rows without moving the reader permalink

Change requestKey with each cursor or retry attempt. The consumer owns fetching and the status slot while the list preserves the visible anchor.

API

TRVirtualList properties

PropType / defaultPurpose
items / itemKey / itemBuilderList<T> / K Function(T) / Widget Function(...) · requiredProvide the ordered data, stable identity, and lazy row builder.
estimatedItemExtentdouble Function(T, int) · requiredEstimates an unmeasured item main-axis extent.
axis / initialPosition / followAxis / TRVirtualListInitialPosition<K> / TRVirtualListFollowChoose vertical or horizontal layout, the fallback starting point, and optional logical-edge following.
leadingEdgeRequest / trailingEdgeRequestTRVirtualListEdgeRequest? · nullSignal proximity to either edge and optionally render a status slot.
initialSnapshot / pageStorageIdTRVirtualListSnapshot<K>? / String?Restore a stable-key anchor explicitly or through PageStorage.
controllerTRVirtualListController<K>? · nullNavigate by index, key, or edge; capture a snapshot; or hold one layout anchor.