flutter_plugin_kit 0.1.0 copy "flutter_plugin_kit: ^0.1.0" to clipboard
flutter_plugin_kit: ^0.1.0 copied to clipboard

Flutter ergonomics for plugin_kit: scope widgets, a State mixin, a ChangeNotifier adapter, and BuildContext.watchEvent / readEvent helpers.

flutter_plugin_kit. Flutter ergonomics for plugin_kit.

pub package License: BSD-3-Clause

Flutter scope widgets, lifecycle-safe event listeners, and state-library adapters for plugin_kit.

By using flutter_plugin_kit instead of plumbing PluginRuntime and PluginSession through InheritedWidgets and cleaning up StreamSubscriptions by hand, you get:

  • Event listeners that auto-cancel on widget dispose and re-attach themselves when the session swaps.
  • Scope widgets that either own or borrow a runtime and session, with loading and error builders for async session creation.
  • Plugin events that drop straight into provider, riverpod, flutter_bloc, signals, or any other listenable consumer.
  • One-line event reads from any BuildContext.

Pulls in only flutter and plugin_kit. Use whichever adapters your app already needs, or skip a state library entirely.

What's in the box #

Scopes #

PluginRuntimeScope and PluginSessionScope push the runtime and session into the widget tree as inherited scopes.

  • Pass an externally-owned runtime via .value and the scope keeps its hands off lifecycle. Same contract as Provider.value.
  • Pass a list of plugins and the scope constructs, inits, and disposes one for you.
  • PluginSessionScope runs in three modes: explicit session, runtime plus auto-created session, or deriving both from an ambient PluginRuntimeScope. Async creation gets optional loading and error builders.

Listening from State #

PluginSessionStateListener<W> is a mixin on State<W> that registers event subscriptions which clean up on dispose and re-attach automatically when the active session swaps.

  • listen<E>(handler) runs handler for every event of type E.
  • rebuildOn<E>([when]) calls setState on the next matching event.
  • Both are callable from initState and any later lifecycle callback.

By default the mixin reads the active session from the ambient PluginSessionScope. Override PluginSession? get session (typically => widget.session) only when the session lives elsewhere.

Notifiers and watchers #

PluginEventNotifier<E> is a ChangeNotifier and ValueListenable<E?> that subscribes to a session and exposes the latest event of type E as .value. It drops directly into ChangeNotifierProvider, ValueListenableProvider, ValueListenableBuilder, and anything else that consumes a listenable.

BuildContext.watchEvent<E>() rebuilds the calling element on the next event of type E. BuildContext.readEvent<E>() returns the latest event of that type without subscribing.

Quick tour #

A scoped app, a State mixin, one listener. ChatPlugin, AssistantPlugin, and ChatMessageReceived stand in for the plugins and events you'd write yourself.

void exampleAppRoot() {
  runApp(
    MaterialApp(
      home: PluginRuntimeScope(
        plugins: [ChatPlugin(), AssistantPlugin()],
        child: const PluginSessionScope(child: ChatScreen()),
      ),
    ),
  );
}

class ChatScreen extends StatefulWidget {
  /// Creates a [ChatScreen].
  const ChatScreen({super.key});

  @override
  State<ChatScreen> createState() => _ChatScreenState();
}

class _ChatScreenState extends State<ChatScreen>
    with PluginSessionStateListener<ChatScreen> {
  String? _last;

  @override
  void initState() {
    super.initState();
    listen<ChatMessageReceived>((envelope) {
      setState(() => _last = envelope.event.text);
    });
  }

  @override
  Widget build(BuildContext context) => Text(_last ?? 'idle');
}

The Builder-only variant is even shorter:

Widget buildWatchEventExample() {
  return Builder(
    builder: (context) {
      final last = context.watchEvent<ChatMessageReceived>();
      return Text(last?.text ?? 'idle');
    },
  );
}

For a runnable version of this pattern plus six other state-library variants, see example/state_garden/ or the live demo.

Integrating with state-management libraries #

flutter_plugin_kit does not depend on provider, flutter_bloc, or any other state library. It exposes standard Flutter shapes that those libraries already consume.

provider #

PluginEventNotifier<E> is a ChangeNotifier, so it drops into ChangeNotifierProvider directly:

ChangeNotifierProvider(
  create: (context) => PluginEventNotifier<ChatMessageReceived>(
    PluginSessionScope.of(context),
  ),
  child: const ChatBody(),
);

// In ChatBody:
final last = context.watch<PluginEventNotifier<ChatMessageReceived>>().value;

It also implements ValueListenable<E?>, so ValueListenableProvider, ValueListenableBuilder, and Listenable.merge all work without ceremony.

flutter_bloc #

No Cubit adapter is bundled; create one by subscribing to session.on<E>:

/// Example Bloc-style cubit that bridges session events.
class PluginEventCubit<E> {
  /// The current event value.
  E? value;

  late final EventSubscription _sub;

  /// Creates a cubit listening to [session] for events of type [E].
  PluginEventCubit(PluginSession session) {
    _sub = session.on<E>((envelope) {
      value = envelope.event;
    });
  }

  /// Cancels the subscription.
  void close() {
    _sub.cancel();
  }
}

Use context.watch<PluginEventCubit<ChatMessageReceived>>().value with this adapter shape. For a real Cubit with .state, see the full recipe (with value-equality state classes) in example/state_garden/lib/src/integrations/bloc_chat.dart.

riverpod / signals / mobx #

The same pattern applies: subscribe in a notifier or store, expose the latest event, dispose cancels. Each library's recipe lives in example/state_garden/ (live demo).

Lifecycle notes #

Ownership #

A scope only owns the runtime or session when it constructed one itself. Pass an external runtime via .value, or an external session via the session: argument, and the caller keeps lifecycle control. Same contract as Provider.value.

Long-lived runtimes #

PluginRuntime and PluginSession are designed to outlive the widget tree (hot restart, route stack resets, deep navigation). For anything that should survive a route swap:

  • Hold the runtime outside the tree. A top-level final, a GetIt singleton, or a Riverpod provider with one-shot create all work.
  • Push it down with PluginRuntimeScope.value or PluginSessionScope(session: ...).
  • Reach for the auto-create variants only when the scope's lifetime is the runtime's lifetime (a per-route StatefulWidget, for example).
  • plugin_kit: the dart-only runtime this package layers on top of. Required.
  • plugin_kit_dialog: drop-in three-tab Flutter UI for inspecting and editing any PluginRuntime. Composes naturally with the scopes shipped here.

Documentation #

License #

BSD 3-Clause. See LICENSE.

0
likes
160
points
56
downloads

Documentation

API reference

Publisher

verified publishersaad-ardati.dev

Weekly Downloads

Flutter ergonomics for plugin_kit: scope widgets, a State mixin, a ChangeNotifier adapter, and BuildContext.watchEvent / readEvent helpers.

Homepage
Repository (GitHub)
View/report issues

Topics

#plugins #flutter #event-bus #state-management #lifecycle

License

BSD-3-Clause (license)

Dependencies

flutter, plugin_kit

More

Packages that depend on flutter_plugin_kit