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
loadinganderrorbuilders 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
.valueand the scope keeps its hands off lifecycle. Same contract asProvider.value. - Pass a list of plugins and the scope constructs, inits, and disposes one for you.
PluginSessionScoperuns in three modes: explicit session, runtime plus auto-created session, or deriving both from an ambientPluginRuntimeScope. Async creation gets optionalloadinganderrorbuilders.
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)runshandlerfor every event of typeE.rebuildOn<E>([when])callssetStateon the next matching event.- Both are callable from
initStateand 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.valueorPluginSessionScope(session: ...). - Reach for the auto-create variants only when the scope's lifetime is the runtime's lifetime (a per-route
StatefulWidget, for example).
Related packages
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 anyPluginRuntime. Composes naturally with the scopes shipped here.
Documentation
- Full docs: plugin-kit.saad-ardati.dev/guides/flutter-plugin-kit/. The dedicated guide.
- API reference: pub.dev dartdoc.
- Source and issues: github.com/SaadArdati/plugin_kit.
License
BSD 3-Clause. See LICENSE.
Libraries
- flutter_plugin_kit
- Flutter ergonomics on top of
plugin_kit.