jolt_surge 1.0.4
jolt_surge: ^1.0.4 copied to clipboard
A lightweight reactive state container for Flutter, built on Jolt Signals and inspired by BLoC's Cubit architecture.
Jolt Surge #
A lightweight, signal-driven state management library for Flutter built on top of Jolt Signals. Jolt Surge provides a predictable state container pattern inspired by BLoC's Cubit, with fine-grained rebuild control, composable listeners, and selector-based rendering.
Documentation #
Overview #
Jolt Surge combines the simplicity and predictability of the Cubit pattern with the reactive capabilities of Jolt Signals. Unlike traditional state management solutions, Surge leverages Jolt's automatic dependency tracking system, enabling you to build highly efficient Flutter applications with minimal boilerplate.
Key Features:
- ๐ฏ Predictable State Management: Simple API with
emit()andstateproperties - ๐ Reactive State Container: Built on Jolt Signals with automatic dependency tracking
- โก Fine-Grained Rebuild Control: Conditional rebuilding and listening with
buildWhenandlistenWhen - ๐จ Composable Listeners: Separate side effects from UI rendering
- ๐ Selector-Based Rendering: Optimize rebuilds with selector functions
- ๐งน Automatic Lifecycle Management: Optional automatic disposal with
SurgeProvider
Core Concepts #
Surge #
A reactive state container that manages state through Jolt Signals. It provides:
state: Get the current state value (reactive, tracked)emit(next): Emit a new state valuedispose(): Clean up resourcesonChange(): Hook for observing state transitions
Widgets #
- SurgeProvider: Provides a Surge instance to the widget tree via
createor.valueconstructors - SurgeConsumer: Unified widget providing both
builderandlistenerfunctionality with conditional controls - SurgeBuilder: Convenience widget for builder-only functionality
- SurgeListener: Convenience widget for listener-only functionality (side effects)
- SurgeSelector: Fine-grained rebuild control using selector functions
Tracking Semantics #
Understanding tracking behavior is crucial for optimal performance:
- Non-tracked (untracked):
builderandlistenerfunctions are executed within an untracked context, preventing unnecessary reactive dependencies - Tracked by default:
buildWhen,listenWhen, andselectorfunctions are tracked by default, allowing them to depend on external signals - Opt-out: To disable tracking, wrap your reads with
untracked(() => ...)or usepeekproperty
Comparison with Cubit #
Jolt Surge's API is ~90% similar to BLoC's Cubit, making it easy to migrate between them. If you're familiar with Cubit, you can seamlessly switch to Surge and experience a signal-powered version of Cubit. However, there are key differences that leverage Jolt's reactive capabilities:
Similarities #
| Feature | Cubit | Surge |
|---|---|---|
| State container | Cubit<State> |
Surge<State> |
| State access | state getter |
state getter |
| State emission | emit(State) |
emit(State) |
| Lifecycle hook | onChange(Change) |
onChange(Change) |
| Disposal | close() |
dispose() |
| Provider pattern | BlocProvider |
SurgeProvider |
| Builder widget | BlocBuilder |
SurgeBuilder |
| Listener widget | BlocListener |
SurgeListener |
| Conditional rebuild | buildWhen |
buildWhen |
Key Differences #
-
Reactive Foundation
- Cubit: Built on Stream, requires explicit subscription management
- Surge: Built on Jolt Signals, automatic dependency tracking and reactive updates
-
State Access
- Cubit:
stateis a simple getter, no automatic dependency tracking - Surge:
stateis reactive and tracked, automatically creates dependencies in Effects and Computed
- Cubit:
-
Signal Integration
- Cubit: Limited ability to integrate with other reactive systems
- Surge: Can depend on external Jolt signals in
buildWhen,listenWhen, andselectorfunctions
-
Performance Optimizations
- Cubit: Relies on Stream-based updates
- Surge: Leverages Jolt's fine-grained dependency tracking for optimal rebuilds
Code Example #
The API is nearly identical, making it easy to switch between Cubit and Surge:
// Cubit
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
void increment() => emit(state + 1);
}
// Surge (signal-powered Cubit)
class CounterSurge extends Surge<int> {
CounterSurge() : super(0);
void increment() => emit(state + 1);
}
The main difference is the underlying reactive system: Cubit uses Streams, while Surge uses Jolt Signals, providing automatic dependency tracking and better performance optimizations. You can easily migrate between them and experience the benefits of signal-based state management.
Quick Start #
Define a Surge #
import 'package:jolt_surge/jolt_surge.dart';
class CounterSurge extends Surge<int> {
CounterSurge() : super(0);
void increment() => emit(state + 1);
void decrement() => emit(state - 1);
@override
void onChange(Change<int> change) {
// optional: observe transitions
}
}
Provide and consume #
SurgeProvider<CounterSurge>(
create: (_) => CounterSurge(), // auto-disposed on unmount
child: SurgeBuilder<CounterSurge, int>(
builder: (context, state, surge) => Text('count: $state'),
),
);
Using .value (you manage lifecycle):
final surge = CounterSurge();
SurgeProvider<CounterSurge>.value(
value: surge, // not auto-disposed
child: SurgeBuilder<CounterSurge, int>(
builder: (context, state, s) => Text('count: $state'),
),
);
Actions (emit state) #
ElevatedButton(
onPressed: () => context.read<CounterSurge>().increment(),
child: const Text('Increment'),
);
Widgets #
SurgeConsumer #
- builder: non-tracked UI build.
- listener: non-tracked side effect (runs when effect recomputes).
- buildWhen(prev, next, surge): tracked condition for rebuilding.
- listenWhen(prev, next, surge): tracked condition for listener.
SurgeConsumer<CounterSurge, int>(
buildWhen: (prev, next, s) => next.isEven, // tracked
listenWhen: (prev, next, s) => next > prev, // tracked
builder: (context, state, s) => Text('count: $state'),
listener: (context, state, s) {
// e.g., SnackBar or analytics
},
);
Disable tracking for a condition:
buildWhen: (prev, next, s) => untracked(() => shouldRebuildSignal.value),
SurgeBuilder #
SurgeBuilder<CounterSurge, int>(
builder: (context, state, s) => Text('count: $state'),
);
SurgeListener #
SurgeListener<CounterSurge, int>(
listener: (context, state, s) {
// side-effect only
},
child: const SizedBox.shrink(),
);
SurgeSelector #
Rebuild only when the selected value changes by equality.
SurgeSelector<CounterSurge, int, String>(
selector: (state, s) => state.isEven ? 'even' : 'odd', // tracked by default
builder: (context, selected, s) => Text(selected),
);
Disable selector tracking:
selector: (state, s) => untracked(() => externalSignal.valueAsLabel(state)),
Advanced Usage #
Custom State Creator #
By default, Surge uses Signal to store state. You can customize the state storage mechanism using the creator parameter:
class CustomSurge extends Surge<int> {
CustomSurge() : super(
0,
creator: (state) => WritableComputed(
() => baseSignal.value,
(value) => baseSignal.value = value,
),
);
}
This is useful when you need to derive state from other signals or implement custom reactive behavior.
SurgeObserver #
Monitor Surge lifecycle events globally using SurgeObserver:
class MyObserver extends SurgeObserver {
@override
void onCreate(Surge surge) {
print('Surge created: $surge');
}
@override
void onChange(Surge surge, Change change) {
print('State changed: ${change.currentState} -> ${change.nextState}');
}
@override
void onDispose(Surge surge) {
print('Surge disposed: $surge');
}
}
// Set global observer
SurgeObserver.observer = MyObserver();
Related Packages #
Jolt Surge is part of the Jolt ecosystem. Explore these related packages:
| Package | Description |
|---|---|
| jolt | Core library providing Signals, Computed, Effects, and reactive collections |
| jolt_flutter | Flutter widgets: JoltBuilder, JoltSelector, JoltProvider |
| jolt_hooks | Hooks API: useSignal, useComputed, useJoltEffect, useJoltWidget |
Acknowledgments #
Jolt Surge is inspired by the Cubit pattern from the BLoC library. We extend our gratitude to the BLoC team for their excellent design patterns and architectural insights that have influenced the development of this library.
License #
This project is part of the Jolt ecosystem. See individual package licenses for details.