ComputedStateWrapper
Renders one of three builders for a ComputedState: a loading builder while the value is computing, a failure builder when it failed, and the main builder once a value is ready. It is the widget counterpart of useComputedState / useAutoComputedState - drop it into a tree to branch on a computed state without pattern-matching the sum type by hand.
class ArticleView extends HookWidget {
const ArticleView({super.key, required this.service, required this.id});
final ArticleService service;
final String id;
Widget build(BuildContext context) {
final articleState = useAutoComputedState(() => service.load(id), keys: [id]);
return ComputedStateWrapper<Article>(
state: articleState,
inProgressBuilder: (context) => const Center(child: CircularProgressIndicator()),
failedBuilder: (context) => const Center(child: Text('Could not load the article')),
builder: (context, article) => Text(article.title), // <- only runs once value is ready
);
}
}
Constructor
ComputedStateWrapper<E>({
Key? key,
required ComputedState<E> state,
required Widget Function(BuildContext) inProgressBuilder,
required Widget Function(BuildContext) failedBuilder,
required Widget Function(BuildContext, E) builder,
bool keepInProgress = false,
});
state- anyComputedState<E>, including theMutableComputedStatereturned byuseComputedState/useAutoComputedState.inProgressBuilder- shown whilestate.valueisnotInitializedorinProgressand no value is available yet.failedBuilder- shown whenstate.valueisfailed.builder- shown once a value is ready; receives the resolvedE(never null).keepInProgress(falseby default) - whentrue, the last ready value stays on screen while a new computation runs, instead of falling back toinProgressBuilder.
The loading and failure builders are wrapped so they fill a scroll view if one is the parent - they render correctly under a RefreshIndicator or sliver without extra Center/SingleChildScrollView boilerplate.
Use cases
-
Rendering a single async value - a detail screen, a card backed by
useAutoComputedState- where you want explicit loading and error UI without writing aHookWidgetand astate.value.when(...)by hand. -
Keeping the previous value visible across a reload. With
keepInProgress: true, switching the input (a newidinkeys) keeps the current content on screen while the next value loads, avoiding a spinner flash:class KeepInProgressView extends HookWidget {const KeepInProgressView({super.key, required this.service, required this.id});final ArticleService service;final String id;Widget build(BuildContext context) {final articleState = useAutoComputedState(() => service.load(id), keys: [id]);return ComputedStateWrapper<Article>(state: articleState,// With keepInProgress, the previous article stays on screen while the next id loads,// instead of flashing the spinner between values.keepInProgress: true,inProgressBuilder: (context) => const Center(child: CircularProgressIndicator()),failedBuilder: (context) => const Center(child: Text('Could not load the article')),builder: (context, article) => Text(article.title),);}}
For a value backed by a RefreshableComputedState that should also support pull-to-refresh, use RefreshableComputedStateWrapper instead. For an iterable that needs a distinct empty state, use ComputedIterableWrapper.
Caveats
-
buildernever receives null. It only runs once a value is ready (or, withkeepInProgress, while the previous value is still held), so there is no nullable case to handle inside it. -
failedBuilderdoes not receive the error. It is aWidget Function(BuildContext)with no exception argument, so it can only render a generic failure state. When you need the error object - to show a message or a retry tied to aRetryable- readstate.valuedirectly with.when(...)instead of using this wrapper. -
keepInProgressshows stale content during the reload. The previous value remains visible while the new computation runs, which is the point - but it does mean the UI is briefly out of date. Don't combine it with a separate spinner that implies fresh data. -
A
notInitializedstate rendersinProgressBuilder, same asinProgress. The wrapper does not distinguish "never started" from "running"; if a state is on-demand and may sit idle, that idle state looks like loading here. UseuseAutoComputedState(which starts computing on build) or branch onstate.valueyourself.
See also
- useComputedState - the on-demand computed state this widget renders
- useAutoComputedState - the auto-refreshing variant, the usual source of
state - ComputedIterableWrapper - adds a dedicated empty builder for collections
- RefreshableComputedStateWrapper - the same wrapper with pull-to-refresh