Getting started
JIntent is a Flutter library for explicit intents, immutable state and one-time UI effects. Controllers manage state; intents perform operations; effect handlers present dialogs, navigation and feedback outside persistent state.
Read the interactive guide (English and Spanish) for the concepts, live demos and the API reference.
Installation
Section titled “Installation”flutter pub add jintentOr declare it yourself. Version 3.0.0 requires Flutter 3.29.2 or newer:
dependencies: jintent: ^3.0.0Import the public entry point:
import 'package:jintent/jintent.dart';Optional pieces have their own entry points, so the core stays small:
| Import | Contents |
|---|---|
package:jintent/jintent.dart |
State, intents, controller, effects, dispatchers, JObserver, Either, use cases, mappers |
package:jintent/devtools.dart |
Structured logging, correlation, metrics, debug overlay |
package:jintent/testing.dart |
State and effect recorders, global reset |
package:jintent/widgets.dart |
Controller builder and selector without a provider package |
package:jintent/undo_redo.dart |
Stable undo/redo, observable history and commands |
package:jintent/navigation.dart |
Thin Navigator abstraction |
package:jintent/utils.dart |
Throttler, validation, color and platform helpers |
See undo and redo for local edit history, UI integration, ordering and migration from the legacy experimental import.
Upgrading from 2.x changes state equality, effect failures and disposal behavior. Review the migration guide first.
What’s new in 3.0.0
Section titled “What’s new in 3.0.0”- Entry points:
jintent.dartholds only the core; devtools, undo/redo, widgets, navigation, utilities and test helpers moved to their own imports. - Predictable state: updates equal to the current state are skipped, so listeners and observers only see real changes.
- Safer effects: an awaited effect without a listener or handler fails with
StateError, and disposal fails pending results. Effect buffering is opt-in. - Concurrency: droppable and restartable dispatchers, with cooperative
cancellation through
JIntent.cancel. - Observability: composable
JObserverDelegates that also report failed, cancelled and dropped intents, plus richer metrics and overlay. - Also new: stable undo/redo with observable history,
JControllerBuilderandJControllerSelector, and test recorders inpackage:jintent/testing.dart.
Several of these change behavior compared with 2.x. See the full changelog and the migration guide.
Minimal example
Section titled “Minimal example”import 'package:jintent/jintent.dart';
class CounterState extends JState { final int count; const CounterState({this.count = 0});
@override CounterState copyWith({int? count}) => CounterState(count: count ?? this.count);
@override List<Object?> get props => [count];}
class IncrementIntent extends JIntent<CounterState> with JIntentHelpers<CounterState> { @override Future<void> onInvoke() async { update((state) => state.copyWith(count: state.count + 1)); }}
class CounterController extends JController<CounterState> { CounterController() : super(const CounterState());
Future<void> increment() => intent(IncrementIntent());
@override void onInit() {}}
Future<void> main() async { final controller = CounterController(); try { await controller.increment(); print(controller.currentState.count); // 1 } finally { controller.dispose(); }}Connect the controller directly with JControllerBuilder or
JControllerSelector from package:jintent/widgets.dart, or use your
StateNotifier integration. The widget guide covers ownership,
controller replacement and selecting part of a state. The
example gallery uses the builders in its four focused labs and
Riverpod with JEffectListener in the counter.
Contracts
Section titled “Contracts”JState: include relevant values inprops; return new immutable states. Updates equal to the current state are skipped.JIntent: overrideonInvokeand await its work. One instance cannot run concurrently; it can be reused after completion.JController: dispatches sequentially by default. Return dispatch futures and handle their errors at the UI boundary.onInitruns synchronously from the base constructor, before the subclass constructor body. PasseffectBufferSizeto retain effects emitted before the UI attaches.JEffect: transient UI events. Register each concrete effect type in aJSideEffectHandlerand complete awaited results. A subscription alone does not guarantee completion; configure a timeout for awaited UI work.JObserver: registerJObserverDelegates to observe intents (including failures), state and effects; see observability.- Disposal rejects queued intents and pending effect results; running service calls and handlers need their own cancellation or lifecycle checks.
See effects for registration, failures and timeouts, and error handling for operation boundaries.