Use Jolt Flutter
package:jolt_flutter/jolt_flutter.dart covers two responsibilities:
- Track Jolt signals from inside a Flutter widget and rebuild it automatically when those signals change.
- Convert between Flutter
Listenable/ValueNotifierand Jolt signals in either direction.
It re-exports package:jolt/jolt.dart, so importing only jolt_flutter brings Signal, Computed, Effect, Watcher, batch, and Readable along. Use use-jolt for pure Dart state with no widget or Listenable boundary.
API Choice Table
| Need | Use |
|---|---|
Rebuild a subtree from every reactive .value read in build |
JoltBuilder |
| Rebuild only from explicitly declared dependencies | JoltBuilder.manual(deps: [...]) |
Rebuild from a single Readable<T> |
JoltWatcher<T> / JoltWatcher.value |
Inline shorthand for one Readable<T> |
readable.watch((v) => ...) |
Rebuild only when a derived value changes by != |
JoltSelector<T> |
| Reactive callback that runs at frame end | PostFrameEffect |
Expose Readable<T> as ValueListenable<T> |
readable.listenable / JoltValueListenable<T> |
Expose Writable<T> as ValueNotifier<T> (two-way) |
writable.notifier / JoltValueNotifier<T> |
Treat ValueListenable<T> as a Jolt Readable<T> |
valueListenable.toListenableSignal() |
Treat ValueNotifier<T> as a Jolt Signal<T> |
valueNotifier.toNotifierSignal() |
Scope precision, narrowest first: JoltSelector > JoltWatcher > JoltBuilder > JoltBuilder.manual.
Widgets
JoltBuilder — auto-tracked rebuilds
Every .value read inside builder subscribes the widget. Multiple changes in one frame coalesce into a single rebuild.
final count = Signal(0);
final doubled = Computed(() => count.value * 2);
JoltBuilder(
builder: (context) => Column(children: [
Text('count: ${count.value}'),
Text('doubled: ${doubled.value}'),
]),
);
JoltBuilder.manual — explicit deps
Reactive reads in builder display fresh values but do not subscribe; only entries in deps drive rebuilds.
JoltBuilder.manual(
deps: [visibleCount],
builder: (context) => Text('${label.value}: ${visibleCount.value}'),
);
JoltWatcher — single readable
JoltWatcher.value omits BuildContext. readable.watch(...) is the shortest form.
JoltWatcher<int>(readable: count, builder: (context, v) => Text('$v'));
count.watch((v) => Text('$v'));
JoltSelector — narrow by a derived value
selector runs in a reactive scope and receives the previous result (null on first run). The widget rebuilds only when the new result is unequal (!=) to the previous one.
final user = Signal(User(name: 'Ada', age: 30));
JoltSelector<String>(
selector: (_) => user.value.name,
builder: (context, name) => Text('Hello $name'),
);
Frame Effects
PostFrameEffect works like core Effect, but notifications coalesce through SchedulerBinding.endOfFrame. Use it for reactive work that touches layout, BuildContext, controllers, focus, or navigation.
late final PostFrameEffect effect;
final size = Signal(Size.zero);
effect = PostFrameEffect(() {
debugPrint('size: ${size.value}');
});
effect.dispose();
Flags: lazy: true defers the first run until run(); detach: true keeps the effect from retaining its enclosing scope; pass debug: JoltDebugOption.type('...') for meaningful labels in debug tools.
Listenable Bridges
Jolt → Flutter
readable.listenable and writable.notifier return cached wrappers (JoltValueListenable<T> / JoltValueNotifier<T>) until disposed. notifier is two-way: writes from either side propagate.
ValueListenableBuilder<int>(
valueListenable: count.listenable,
builder: (context, v, _) => Text('$v'),
);
final controller = count.notifier;
controller.value = 1; // count.value == 1
count.value = 2; // controller.value == 2
Dispose long-lived bridges when their owner ends; the extension getter creates a fresh wrapper on next access.
Flutter → Jolt
toListenableSignal() returns a read-only Readable<T>; toNotifierSignal() returns a writable Signal<T>. If the source is already a JoltValueListenable / JoltValueNotifier, the original Jolt node is returned — no extra wrapper.
final tab = ValueNotifier(0);
final readableTab = tab.toListenableSignal();
JoltBuilder(builder: (context) => Text('tab: ${readableTab.value}'));
final textCtrl = ValueNotifier('');
final textSignal = textCtrl.toNotifierSignal();
textSignal.value = 'ready'; // textCtrl.value == 'ready'
ValueListenableSignal<T> and ValueNotifierSignal<T> are the concrete cached types. Prefer the extension methods unless a test or integration needs the concrete type. After disposal these bridges keep their last value; writes to a disposed writable bridge are ignored.
Rules
- Create mutable Jolt state outside
build(in a model,State, or owner) and dispose owners explicitly. - Match the widget to the dependency surface: prefer the narrowest that still captures the truly UI-driving reads.
- Use
JoltBuilder.manualwhen.valuereads inbuildare incidental and should not subscribe. - Reach for
PostFrameEffectonly when the callback needs frame timing or Flutter context; use coreEffectfor plain reactions. - Dispose bridges, frame effects, and owned signals when the Flutter owner is disposed.
Source Navigation
Read source before answering API-sensitive questions.
| Area | Path |
|---|---|
| Public exports | packages/jolt_flutter/lib/jolt_flutter.dart |
| Reactive widgets | packages/jolt_flutter/lib/src/widgets/ |
| Frame effect | packages/jolt_flutter/lib/src/effect/post_frame_effect.dart |
| Listenable bridges | packages/jolt_flutter/lib/src/listenable/ |
| Example | packages/jolt_flutter/example/jolt_flutter_example.dart |
| Tests | packages/jolt_flutter/test/ |
Source: vowdemon/jolt — distributed by TomeVault.