Provides a shard to the widget tree and manages its lifecycle.
// Create new shard
ShardProvider<CounterShard>(
create: () => CounterShard(),
child: MyWidget(),
)
// Provide existing shard
ShardProvider.value(
value: existingShard,
child: MyWidget(),
)
// Access shard
final shard = context.read<CounterShard>();
Simplifies providing multiple shards to the widget tree. Automatically nests multiple ShardProvider instances.
// Provide multiple shards
MultiShardProvider(
providers: [
ShardProvider<CounterShard>(create: () => CounterShard()),
ShardProvider<AuthShard>(create: () => AuthShard()),
ShardProvider<SettingsShard>(create: () => SettingsShard()),
],
child: MyApp(),
)
// Mix create and value constructors
final existingShard = CounterShard();
MultiShardProvider(
providers: [
ShardProvider<CounterShard>.value(value: existingShard),
ShardProvider<AuthShard>(create: () => AuthShard()),
],
child: MyApp(),
)
// Access shards
final counterShard = context.read<CounterShard>();
final authShard = context.read<AuthShard>();
Note: Shards created with create are automatically disposed when MultiShardProvider is removed. Shards provided with value are not disposed automatically.
Rebuilds when state changes. Provides the current state to the builder.
ShardBuilder<CounterShard, int>(
builder: (context, count) => Text('Count: $count'),
)
// Conditional rebuilding
ShardBuilder<CounterShard, int>(
buildWhen: (prev, curr) => curr % 2 == 0,
builder: (context, count) => Text('Even: $count'),
)
// Listener callback
ShardBuilder<CounterShard, int>(
listener: (prev, curr) => print('$prev → $curr'),
builder: (context, count) => Text('Count: $count'),
)
// Control listening
ShardBuilder<CounterShard, int>(
listenWhen: (prev, curr) => curr > 10,
builder: (context, count) => Text('Count: $count'),
)
Recommended approach. Rebuilds only when the selected value changes. Optimizes performance by avoiding unnecessary rebuilds.
// Select part of state
ShardSelector<TodoShard, TodoState, int>(
selector: (state) => state.todos.length,
builder: (context, count) => Text('Count: $count'),
)
// Complex selection
ShardSelector<TodoShard, TodoState, List<Todo>>(
selector: (state) => state.todos.where((t) => !t.completed).toList(),
builder: (context, activeTodos) => Text('Active: ${activeTodos.length}'),
)
// Simple state access
ShardSelector<CounterShard, int, int>(
selector: (state) => state,
builder: (context, count) => Text('Count: $count'),
)
For async operations with FutureShard or StreamShard, use AsyncShardBuilder with separate callbacks:
AsyncShardBuilder<UserShard, User>(
onLoading: (context) => CircularProgressIndicator(),
onData: (context, user) => Text('Hello, ${user.name}'),
onError: (context, error, stackTrace) => Text('Error: $error'),
)
AsyncShardBuilder also accepts an optional onIdle builder for the not-yet-started state (for example, before a CommandShard has run). It defaults to the onLoading widget, so existing call sites are unaffected:
// onIdle renders the not-yet-started state (defaults to the loading widget).
// Typically used with a CommandShard.
AsyncShardBuilder<SubmitCommand, User>(
shard: submit,
onIdle: (context) => const Text('Tap submit to begin'),
onLoading: (context) => const CircularProgressIndicator(),
onData: (context, user) => Text('Welcome, ${user.name}'),
onError: (context, error, stackTrace) => Text('Error: $error'),
)
onIdle is optional and falls back to onLoading.You can also use ShardBuilder with AsyncValue and pattern matching for more control:
ShardBuilder<UserShard, AsyncValue<User>>(
builder: (context, asyncValue) {
return switch (asyncValue) {
AsyncIdle() => const SizedBox.shrink(),
AsyncLoading() => const CircularProgressIndicator(),
AsyncData(:final data) => Text('Hello, ${data.name}'),
AsyncError(:final error) => Text('Error: $error'),
};
},
)
Prefer asyncValue.when(...) for exhaustive matching — see Async Values.
ShardListener is the listener-only counterpart to ShardBuilder. It invokes a callback on state changes to run side effects — such as navigation or showing snackbars — without rebuilding. Its child is rendered as-is and is never rebuilt by the listener. Use it for effects you should not run from a builder.
// Runs side effects (navigation, snackbars) on state changes WITHOUT rebuilding.
ShardListener<AuthShard, AuthState>(
listenWhen: (prev, curr) => curr.isLoggedIn && !prev.isLoggedIn,
listener: (context, prev, curr) {
Navigator.of(context).pushReplacementNamed('/home');
},
child: const LoginForm(),
)
The shard parameter is optional — when omitted, the shard is taken from the enclosing ShardProvider. Use listenWhen to control when the listener fires.
MultiShardListener nests several ShardListener instances around a single child, outermost first, mirroring MultiShardProvider. Each entry may omit its own child:
MultiShardListener(
listeners: [
ShardListener<AuthShard, AuthState>(listener: _onAuth),
ShardListener<CartShard, CartState>(listener: _onCart),
],
child: const HomePage(),
)
Next: Async Values for loading, data, and error states with FutureShard and StreamShard.
See also: CommandShard.