Scoping a runtime
ahp_flutter is built on InheritedWidget, InheritedNotifier, and
ValueNotifier.
There is no state-management package to adopt alongside the protocol.
flutter pub add ahp_flutterIt re-exports the whole client, so one import is enough.
Providing
Section titled “Providing”AhpProvider( runtime: AhpRuntime( connect: () async => AhpConnection( await WebSocketAhpTransport.connect(Uri.parse('ws://localhost:51234')), ), clientId: 'my-app', ), child: const MyApp(),);Two scopes, not one
Section titled “Two scopes, not one”The provider installs two inherited layers, and the split is the point.
context.ahp returns the runtime and does not subscribe to rebuilds.
The runtime is identity-stable for the life of the tree, so depending on it
would cost an element registration and buy a rebuild that can never fire.
context.ahpConnection returns the lifecycle and does rebuild, on every
transition.
final state = context.ahpConnection;if (state.phase == AhpLifecycle.reconnecting) { return Text('reconnecting (attempt ${state.attempt})');}A single scope carrying both would rebuild every dependent on every connection transition — including subtrees that only ever needed the runtime to dispatch through. A widget test pins the distinction: the lifecycle watcher rebuilds while the runtime-only watcher does not.
Watching a channel
Section titled “Watching a channel”AhpChannel<RootState>( channel: Uri.parse('ahp-root://'), builder: (context, root, _) => Text('${root.agents.length} agents'),);For a State that needs several channels, use the mixin:
class _MyPageState extends State<MyPage> with AhpChannelMixin<MyPage> { @override Widget build(BuildContext context) => ValueListenableBuilder( valueListenable: ahpWatch<ChatState>(widget.chatUri), builder: (context, chat, _) => …, );}Leases are shared
Section titled “Leases are shared”Two widgets watching the same channel share one wire subscription and one store. Release is idempotent, and the last release starts a grace window before teardown, so a push-and-pop does not churn the wire.
Testing
Section titled “Testing”Widget tests can drive a real AhpRuntime against a fake AhpClient.
Two settings keep its timers from outliving the tree and tripping
flutter_test’s pending-timer assertion:
AhpRuntime( connect: () async => fakeClient, clientId: 'test', keepaliveInterval: null, // no periodic ping lingerDuration: Duration.zero, // tear subscriptions down inline);