Skip to main content

RefreshableComputedIterableWrapper

A ComputedIterableWrapper wrapped in a RefreshIndicator. It takes a RefreshableComputedState whose value is an Iterable, adds pull-to-refresh, and keeps the four-way loading / failure / empty / non-empty branching.

class InboxView extends HookWidget {
const InboxView({super.key, required this.service});

final InboxService service;


Widget build(BuildContext context) {
final messagesState = useAutoComputedState(service.load);

// RefreshableComputedListWrapper<Message> is the List<Message> shorthand for this.
return RefreshableComputedIterableWrapper<List<Message>>(
state: messagesState,
inProgressBuilder: (context) => const Center(child: CircularProgressIndicator()),
failedBuilder: (context) => const Center(child: Text('Could not load messages')),
emptyBuilder: (context) => const Center(child: Text('Inbox zero')),
builder: (context, messages) => ListView(
children: [for (final message in messages) Text(message.text)],
),
);
}
}

Constructor

RefreshableComputedIterableWrapper<I extends Iterable<dynamic>>({
Key? key,
required RefreshableComputedState<I> state,
required Widget Function(BuildContext) inProgressBuilder,
required Widget Function(BuildContext) failedBuilder,
required Widget Function(BuildContext) emptyBuilder,
required Widget Function(BuildContext, I) builder,
bool keepInProgress = false,
});

typedef RefreshableComputedListWrapper<E> = RefreshableComputedIterableWrapper<List<E>>;

RefreshableComputedListWrapper<E> is a shorthand for the common List<E> case, so the type argument reads <Message> instead of <List<Message>>:

class InboxViewWithAlias extends HookWidget {
const InboxViewWithAlias({super.key, required this.service});

final InboxService service;


Widget build(BuildContext context) {
final messagesState = useAutoComputedState(service.load);

// Same widget, less noise: the List<E> type argument is implied.
return RefreshableComputedListWrapper<Message>(
state: messagesState,
inProgressBuilder: (context) => const Center(child: CircularProgressIndicator()),
failedBuilder: (context) => const Center(child: Text('Could not load messages')),
emptyBuilder: (context) => const Center(child: Text('Inbox zero')),
builder: (context, messages) => ListView(
children: [for (final message in messages) Text(message.text)],
),
);
}
}

Use cases

  • A list screen that should support pull-to-refresh and has a meaningful empty state - an inbox with "Inbox zero", a non-paged search with "No matches". This is the collection counterpart of RefreshableComputedStateWrapper.
  • Prefer the RefreshableComputedListWrapper<E> alias for List<E> values; it is the same widget with less type noise.

When the list is paged rather than loaded whole, reach for PaginatedComputedStateWrapper instead - it adds load-more-on-scroll, which this widget does not.

Caveats

  • builder must return a scrollable, for the same reason as RefreshableComputedStateWrapper: the RefreshIndicator needs a scrolling child to arm the pull. The empty, loading, and failure builders are made scroll-fillable by the wrapper, so an empty state can still be pulled to refresh.

  • This wrapper refreshes the whole computation; it does not paginate. There is no loadMore, no cursor. For endless lists use PaginatedComputedStateWrapper.

  • All ComputedIterableWrapper semantics carry over: emptiness is Iterable.isEmpty on the ready value, failedBuilder gets no error, and notInitialized renders as loading.

See also