joker_state 4.2.0+3 copy "joker_state: ^4.2.0+3" to clipboard
joker_state: ^4.2.0+3 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.

Chinese

🃏 JokerState #

⚠️ Major Refactor Notice:

  • No More RxDart: The package has been completely refactored to remove the dependency on rxdart.
  • Built on ChangeNotifier: The core is now built on Flutter's native ChangeNotifier for a simpler, more lightweight, and predictable API.
  • Simplified API: Joker and Presenter now share a common base class, JokerAct, simplifying the overall architecture.
  • New DI Methods: Dependency injection via BuildContext has been streamlined. Use context.joker<T>() to read a value and context.watchJoker<T>() to listen for changes.

JokerState is a lightweight, reactive Flutter state management toolkit built on ChangeNotifier, with integrated dependency injection. With the Joker, Presenter, and UI binding widgets, you can flexibly manage state and dramatically reduce boilerplate.

pub package License: MIT

Features #

  • 🧠 Reactive State Management: Automatic widget rebuilds and side-effect execution, powered by ChangeNotifier.
  • 💉 Simple Dependency Injection: Easily provide Joker or Presenter instances to the widget tree.
  • 🪄 Selective Rebuilds: Fine-grained control over what triggers UI updates to optimize performance.
  • 🔄 Batch Updates: Combine multiple state changes into a single UI notification.
  • 🏗️ Record Support: Combine multiple states into a single view using Dart Records with JokerTroupe.
  • 🧩 Modular Design: A clear separation between state logic and UI widgets.
  • 📢 Event Bus: A type-safe event system is available for decoupled communication.
  • ⏱️ Timing Controls: Debounce and throttle utilities to manage frequent events.

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 lightweight state container based on ChangeNotifier. It's perfect for managing simple, local state. Its lifecycle is managed by its listeners and the keepAlive parameter.

// Create a Joker (auto-notifies by default)
final counter = Joker<int>(0);

// Update state and notify all listeners
counter.trick(1);

// Or simply use the setter
counter.state = 2;

// Update using a function
counter.trickWith((current) => current + 1);

✨ Presenter: State Management with Lifecycle #

Presenter<T> is an advanced version of Joker. It includes lifecycle hooks (onInit, onReady, onDone), making it ideal for complex business logic and implementing patterns like BLoC or MVVM.

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();

🎪 JokerRing & CircusRing: Dependency Injection #

Context-based DI with JokerRing

Use JokerRing to provide a Joker or Presenter to the widget tree. Descendants can then access the instance using context extensions.

// 1. Provide the Joker/Presenter
JokerRing<int>(
  act: myPresenter,
  child: MyScreen(),
);

// 2. Access it in a descendant widget
class MyScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Use watchJoker to listen for changes and rebuild
    final count = context.watchJoker<int>().value;

    return Scaffold(
      body: Text('Count: $count'),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // Use joker() to get the instance without listening
          // Cast it to the concrete type to access its methods
          final presenter = context.joker<int>() as MyCounterPresenter;
          presenter.increment();
        },
      ),
    );
  }
}

Context-less DI with CircusRing

For accessing dependencies from outside the widget tree (e.g., in a service or another Presenter), you can use CircusRing as a service locator.

// 1. Register a dependency (e.g., in main.dart)
Circus.hire<ApiService>(ApiService());

// 2. Find the dependency anywhere, without BuildContext
class AuthPresenter extends Presenter<AuthState> {
  final _apiService = Circus.find<ApiService>();

  Future<void> login(String user, String pass) async {
    final result = await _apiService.login(user, pass);
    // ... update state
  }
}

🎭 Simple Reactive UI Integration #

JokerState provides extension methods on any JokerAct (Joker or Presenter) for seamless UI integration.

// Rebuild a widget when the state changes
counterJoker.perform(
  builder: (context, count) => Text('Count: $count'),
);

// Rebuild only when a specific part of the state changes
userJoker.focusOn<String>(
  selector: (user) => user.name,
  builder: (context, name) => Text('Name: $name'),
);

For more details, see State Management.

📢 Event Bus & ⏱️ Timing Controls #

The package also includes a robust, type-safe event bus (RingCueMaster) and timing control utilities (CueGate) for throttling and debouncing events. These tools are independent of the state management core but integrate well with it.

Example: Debouncing search queries with CueGate and RingCueMaster

// Define a search event
class SearchQueryChanged {
  final String query;
  SearchQueryChanged(this.query);
}

// Create a debounce gate
final searchGate = CueGate.debounce(delay: const Duration(milliseconds: 300));

// In your UI:
TextField(
  onChanged: (text) {
    // Trigger the gate. The action will only run after 300ms of inactivity.
    searchGate.trigger(() {
      // Send the event through the event bus
      Circus.cue(SearchQueryChanged(text));
    });
  },
);

// In your Presenter or another service, listen for the debounced event:
class SearchPresenter extends Presenter<List<String>> {
  SearchPresenter() : super([]) {
    // Listen for debounced search queries
    Circus.onCue<SearchQueryChanged>((event) {
      _performSearch(event.query);
    });
  }

  void _performSearch(String query) {
    // ... your search logic
  }
}

For more details, see their respective READMEs within the lib directory.

When to use JokerState #

  • You want a simpler alternative to complex state management solutions.
  • You need reactive UI updates with minimal boilerplate.
  • You want the flexibility of both automatic and manual state notifications.
  • You need a simple, integrated dependency injection solution.
  • You prefer clear, direct state manipulation.

License #

MIT

1
likes
150
points
103
downloads

Publisher

unverified uploader

Weekly Downloads

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.

Repository (GitHub)

Documentation

API reference

License

MIT (license)

Dependencies

circus_ring, flutter, provider

More

Packages that depend on joker_state