Surge Widgets
Surge provides multiple Widgets for using Surge state management in Flutter. These Widgets are based on Jolt's reactive system, providing BLoC-like APIs while maintaining compatibility with the Jolt ecosystem.
SurgeProvider
SurgeProvider is used to provide Surge instances in the Widget tree, similar to how Provider works. It supports two constructors: create and .value.
Using create Constructor
When using the create constructor, Surge lifecycle is automatically managed. When the Widget is removed, surge.dispose() is automatically called.
SurgeProvider<CounterSurge>(
create: (_) => CounterSurge(), // Automatically disposed on unmount
child: SurgeBuilder<CounterSurge, int>(
builder: (context, state, surge) => Text('count: $state'),
),
);Using .value Constructor
When using the .value constructor, Surge lifecycle needs manual management. Surge will not be automatically disposed when the Widget is removed.
// Singleton Surge, managed elsewhere
final surge = CounterSurge();
SurgeProvider<CounterSurge>.value(
value: surge, // Not automatically disposed
child: SurgeBuilder<CounterSurge, int>(
builder: (context, state, s) => Text('count: $state'),
),
);Accessing from Descendant Widgets
// Get Surge instance
final surge = context.read<CounterSurge>();
// Trigger state changes
ElevatedButton(
onPressed: () => surge.increment(),
child: const Text('Increment'),
);Parameters
create: Function to create Surge instance (when usingcreateconstructor)value: Surge instance (when using.valueconstructor)lazy: Whether to lazy create (default true)child: Child Widget
SurgeBuilder
SurgeBuilder is a convenient Widget for building UI based on Surge state changes. It's a simplified version of SurgeConsumer, providing only builder functionality.
Cubit-Compatible API
// 100% compatible API with BlocBuilder
SurgeBuilder<CounterSurge, int>(
builder: (context, state) => Text('Count: $state'),
buildWhen: (prev, next) => next.isEven, // Only rebuild on even numbers
);Full API
SurgeBuilder<CounterSurge, int>.full(
builder: (context, state, surge) => Text('Count: ${surge.state}'),
buildWhen: (prev, next, s) => next.isEven, // Only rebuild on even numbers
);Parameters
builder: Function to build UI, receives(context, state)or(context, state, surge)buildWhen: Conditional function to control rebuilding (optional)surge: Surge instance (optional, defaults to getting from context)
SurgeListener
SurgeListener is a convenient Widget for listening to Surge state changes and executing side effects. It does not rebuild child Widgets, only executes the listener function.
Cubit-Compatible API
// 100% compatible API with BlocListener
SurgeListener<CounterSurge, int>(
listenWhen: (prev, next) => next > prev, // Only listen on increase
listener: (context, state) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Count increased to: $state')),
);
},
child: const SizedBox.shrink(),
);Full API
SurgeListener<CounterSurge, int>.full(
listenWhen: (prev, next, s) => next > prev, // Only listen on increase
listener: (context, state, surge) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Count increased to: $state')),
);
},
child: const SizedBox.shrink(),
);Parameters
listener: Function to handle side effects, receives(context, state)or(context, state, surge)listenWhen: Conditional function to control listener execution (optional)child: Child Widget (not rebuilt)surge: Surge instance (optional, defaults to getting from context)
SurgeConsumer
SurgeConsumer is a unified Widget that provides both builder and listener functionality. It offers fine-grained control, allowing separate control over when to rebuild UI and when to execute side effects.
How It Works
- builder: Builds UI, default behavior is untracked (does not create reactive dependencies), only rebuilds when
buildWhenreturns true - listener: Handles side effects (such as showing SnackBar, sending analytics events, etc.), default behavior is untracked, only executes when
listenWhenreturns true - buildWhen: Controls whether to rebuild, default is tracked (can depend on external signals)
- listenWhen: Controls whether to execute listener, default is tracked (can depend on external signals)
Cubit-Compatible API
SurgeConsumer<CounterSurge, int>(
buildWhen: (prev, next) => next.isEven, // Only rebuild on even numbers
listenWhen: (prev, next) => next > prev, // Only listen on increase
builder: (context, state) => Text('count: $state'),
listener: (context, state) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Count is now: $state')),
);
},
);Full API
SurgeConsumer<CounterSurge, int>.full(
buildWhen: (prev, next, s) => next.isEven, // Only rebuild on even numbers
listenWhen: (prev, next, s) => next > prev, // Only listen on increase
builder: (context, state, surge) => Text('count: $state'),
listener: (context, state, surge) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Count is now: $state')),
);
},
);Using External Signals
buildWhen and listenWhen are tracked by default and can depend on external signals. If you need to use external signals in an untracked manner, use untracked:
SurgeConsumer<CounterSurge, int>.full(
buildWhen: (prev, next, s) => untracked(() => shouldRebuildSignal.value),
// ...
);Parameters
builder: Function to build UI, receives(context, state)or(context, state, surge)listener: Function to handle side effects, receives(context, state)or(context, state, surge)buildWhen: Conditional function to control rebuilding (optional, tracked by default)listenWhen: Conditional function to control listener execution (optional, tracked by default)surge: Surge instance (optional, defaults to getting from context)
SurgeSelector
SurgeSelector provides fine-grained rebuild control, using a selector function to select the value to track. The Widget only rebuilds when the value returned by selector changes.
How It Works
SurgeSelector internally uses EffectScope and Effect to track dependencies. It executes dependency tracking in the selector function, then compares the selector's return value with the previous value. Only when the return value changes (using equality comparison) does it trigger Widget rebuild.
Cubit-Compatible API
// 100% compatible API with BlocSelector
SurgeSelector<CounterSurge, int, String>(
selector: (state) => state.isEven ? 'even' : 'odd',
builder: (context, selected) => Text('Number is $selected'),
);
// Only rebuilds when state switches between even and oddFull API
SurgeSelector<CounterSurge, int, String>.full(
selector: (state, surge) => state.isEven ? 'even' : 'odd',
builder: (context, selected, surge) => Text('Number is $selected'),
);Using External Signals
The selector function is tracked by default and can depend on external signals. If you need to use external signals in an untracked manner, use untracked:
SurgeSelector<CounterSurge, int, String>.full(
selector: (state, s) => untracked(() => externalSignal.valueAsLabel(state)),
builder: (context, selected, s) => Text(selected),
);Parameters
builder: Function to build UI, receives(context, selected)or(context, selected, surge)selector: Function to extract value from state, receives(state)or(state, surge)surge: Surge instance (optional, defaults to getting from context)
Complete Example
class CounterSurge extends Surge<int> {
CounterSurge() : super(0);
void increment() => emit(state + 1);
void decrement() => emit(state - 1);
}
class CounterApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SurgeProvider<CounterSurge>(
create: (_) => CounterSurge(),
child: MaterialApp(
home: CounterPage(),
),
);
}
}
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final surge = context.read<CounterSurge>();
return Scaffold(
body: Center(
child: SurgeBuilder<CounterSurge, int>(
builder: (context, state) => Text('Count: $state'),
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
onPressed: () => surge.increment(),
child: Icon(Icons.add),
),
SizedBox(height: 8),
FloatingActionButton(
onPressed: () => surge.decrement(),
child: Icon(Icons.remove),
),
],
),
);
}
}Important Notes
API Compatibility: Surge Widgets provide 100% compatible APIs with BLoC, making it easy to migrate from Bloc/Cubit.
Performance Optimization: Using
buildWhen,listenWhen, andSurgeSelectorallows precise control over rebuilds and side effect execution, optimizing performance.Reactive Tracking:
buildWhen,listenWhen, andselectorare tracked by default and can depend on external signals. Useuntrackedto avoid tracking.Lifecycle Management: When using the
createconstructor, Surge lifecycle is automatically managed. When using the.valueconstructor, manual management is required.Type Safety: All Widgets provide complete type safety with compile-time type checking.