joker_state 4.0.0
joker_state: ^4.0.0 copied to clipboard
Joker is a lightweight, reactive state management solution for Flutter that brings the fun of the circus to your codebase with simple, flexible, and type-safe state handling.
🃏 JokerState #
⚠️ Breaking Changes in v4.0.0:
CircusRingis now a standalone package. While still usable in JokerState, it no longer provides Joker-specific integration. Please use circus_ring.RingCueMasternow leveragesrx_dartfor a more robust Event Bus system.JokerStageandJokerFrameconstructors are now private. Please use theperformandfocusOnAPIs instead.- Both
JokerandPresenterare now based onRxInterface, providing more flexible and efficient state management. RxInterfaceis built onBehaviorSubjectand internally usesTimerfor improved autoDispose handling.JokerPortalandJokerCastare deprecated. For context-free state management, use CircusRing API withPresenter.JokerRevealis deprecated. Use Dart's native language features for conditional rendering.JokerTrapis deprecated. UsePresenter'sonDoneorStatefulWidget'sdisposefor controller management.
JokerState is a lightweight, reactive Flutter state management toolkit based on rx_dart, with integrated dependency injection via circus_ring.
With just the Joker, Presenter, and CircusRing APIs, you can flexibly manage state and dramatically reduce boilerplate.
Features #
- 🧠 Reactive State Management: Automatic widget rebuilds and side-effect execution.
- 💉 Dependency Injection: Simple DI with the CircusRing API.
- 🪄 Selective Rebuilds: Fine-grained control over what triggers UI updates.
- 🔄 Batch Updates: Combine multiple state changes into a single notification.
- 🏗️ Record Support: Combine multiple states using Dart Records.
- 🧩 Modular Design: Import only what you need, or use the full package.
- 📢 Event Bus: Type-safe event system via RingCueMaster.
- ⏱️ Timing Controls: Debounce, throttle, and more for smooth UX.
Quick Start #
Add JokerState to your pubspec.yaml:
dependencies:
joker_state: ^latest_version
Then import the package:
import 'package:joker_state/joker_state.dart';
Core Concepts #
🎭 Joker: Local Reactive State Container #
Joker<T> is a local reactive state container extending ChangeNotifier. Its lifecycle is managed by listeners and the keepAlive flag.
// Create a Joker (auto-notifies by default)
final counter = Joker<int>(0);
// Update state and notify all listeners
counter.trick(1);
// Update using a function
counter.trickWith((current) => current + 1);
// Batch multiple updates, notify once
counter.batch()
.apply((s) => s * 2)
.apply((s) => s + 10)
.commit();
// Persistent Joker (remains alive even without listeners)
final persistentState = Joker<String>("initial", keepAlive: true);
For manual notification mode:
// Create with autoNotify off
final manualCounter = Joker<int>(0, autoNotify: false);
// Silent updates
manualCounter.whisper(5);
manualCounter.whisperWith((s) => s + 1);
// Notify listeners when ready
manualCounter.yell();
Lifecycle: By default (keepAlive: false), Joker schedules itself for disposal (via microtask) when its last listener is removed. Adding a listener cancels disposal. Set keepAlive: true to keep it alive until manually disposed.
✨ Presenter #
Presenter<T> is built on BehaviorSubject<T> and provides onInit, onReady, and onDone lifecycle hooks—perfect for BLoC, MVC, or MVVM patterns.
class MyCounterPresenter extends Presenter<int> {
MyCounterPresenter() : super(0);
@override
void onInit() { /* Initialization */ }
@override
void onReady() { /* Safe to interact with WidgetsBinding */ }
@override
void onDone() { /* Clean up resources */ }
void increment() => trickWith((s) => s + 1);
}
// Usage:
final myPresenter = MyCounterPresenter();
myPresenter.increment();
// dispose() automatically calls onDone()
myPresenter.dispose();
🎪 CircusRing: Dependency Injection #
CircusRing is a lightweight dependency container, now a standalone package (circus_ring), but still usable within JokerState.
🎭 Simple Reactive UI Integration #
JokerState provides various widgets for seamless state and UI integration:
The Simplest Usage
// Using Joker
final userJoker = Joker<User>(...);
userJoker.perform(
builder: (context, user) => Text('Name: ${user.name}'),
)
// Using Presenter
final myPresenter = MyPresenter(...);
myPresenter.perform(
builder: (context, state) => Text('State: $state'),
)
For more details, see State Management.
📢 RingCueMaster: Event Bus System #
Type-safe event bus for communication between components:
// Define event type
class UserLoggedIn extends Cue {
final User user;
UserLoggedIn(this.user);
}
// Access the global event bus
final cueMaster = Circus.ringMaster();
// Listen for events
final subscription = Circus.onCue<UserLoggedIn>((event) {
print('User ${event.user.name} logged in at ${event.timestamp}');
});
// Send event
Circus.sendCue(UserLoggedIn(currentUser));
// Cancel subscription when done
subscription.cancel();
For more details, see Event Bus.
⏱️ CueGate: Timing Controls #
Manage actions with debounce and throttle:
// Create a debounce gate
final debouncer = CueGate.debounce(delay: Duration(milliseconds: 300));
// Use in event handlers
TextField(
onChanged: (value) {
debouncer.trigger(() => performSearch(value));
},
),
// Create a throttle gate
final throttler = CueGate.throttle(interval: Duration(seconds: 1));
// Limit UI updates
scrollController.addListener(() {
throttler.trigger(() => updatePositionIndicator());
});
// In StatefulWidget, use the mixin for automatic cleanup
class SearchView extends StatefulWidget {
// ...
}
class _SearchViewState extends State<SearchView> with CueGateMixin {
void _handleSearchInput(String query) {
debounceTrigger(
() => _performSearch(query),
Duration(milliseconds: 300),
);
}
void _handleScroll() {
throttleTrigger(
() => _updateScrollPosition(),
Duration(milliseconds: 100),
);
}
// Cleanup handled automatically by mixin
}
For more details, see Timing Controls.
Advanced Features #
🔄 Side-Effects #
Listen for state changes and execute side-effects:
final counter = Joker<int>(0);
counter.effect(
child: Container(),
effect: (context, state) {
print('State changed: $state');
},
runOnInit: true,
effectWhen: (prev, val) => (prev!.value ~/ 5) != (val.value ~/ 5),
);
Additional Info #
JokerState is designed to be lightweight, flexible, and powerful—offering reactive state management and dependency injection in one cohesive package.
When should you use JokerState? #
- You want something simpler than BLoC or other complex state solutions
- You need reactive UI updates with minimal boilerplate
- You want the flexibility to control things manually when needed
- You need integrated dependency management
- You prefer clear, direct state operations (not abstract concepts)
- You need a type-safe event bus for decoupled communication
- You want utility widgets that work well with your state management
License #
MIT