davianspace_hosting_flutter
Enterprise-grade Flutter integration for the DavianSpace hosting runtime. Bridges dependency injection, hosted-service lifecycle management, configuration, and structured logging into Flutter's widget tree — conceptually equivalent to adding Microsoft.Extensions.Hosting support to a Flutter app.
Table of contents
- Features
- Installation
- Quick start
- Ecosystem integration
- Resolving services
- Architecture
- Advanced usage
- API reference
- Error handling
- Testing
- Contributing
- Security
- License
Features
| Feature | Description |
|---|---|
| ServiceProviderScope | InheritedWidget that exposes the DI container to the entire widget tree |
| BuildContext extensions | getService<T>(), tryGetService<T>(), getAllServices<T>(), keyed variants, and serviceProvider |
| FlutterHostRunner | Host.runFlutterApp() / Host.runFlutterAppAsync() — imperative one-liner startup |
| HostProvider | Declarative StatefulWidget — loading/error/ready states, lifecycle observer |
| Lifecycle integration | Automatic Host.stop() and Host.dispose() on widget disposal and AppLifecycleState.detached |
| Zero reflection | No dart:mirrors, no code generation — full AOT and tree-shaking support |
| State-management agnostic | Works with Provider, Bloc, Riverpod, or any other approach |
Installation
Add to your pubspec.yaml:
dependencies:
davianspace_hosting_flutter: ^1.0.3
Then run:
flutter pub get
Quick start
Imperative (runFlutterApp)
The simplest approach — start the host and mount the widget tree in one call:
import 'package:davianspace_hosting/davianspace_hosting.dart';
import 'package:davianspace_hosting_flutter/davianspace_hosting_flutter.dart';
import 'package:flutter/material.dart';
void main() async {
final host = await createDefaultBuilder()
.configureServices((ctx, services) {
services.addInstance<GreetingService>(
const GreetingService('Hello!'),
);
})
.build();
await host.runFlutterApp(() => const MyApp());
}
Lifecycle flow:
main() → host.build() → host.start() → runApp(ServiceProviderScope → MyApp)
↓ (on dispose / detach)
host.stop() → host.dispose()
Async imperative (runFlutterAppAsync)
Show a loading indicator while the host starts:
void main() async {
final host = await createDefaultBuilder().build();
await host.runFlutterAppAsync(
() => const MyApp(),
loadingWidget: const MaterialApp(
home: Scaffold(body: Center(child: CircularProgressIndicator())),
),
errorBuilder: (error) => MaterialApp(
home: Scaffold(body: Center(child: Text('Error: $error'))),
),
);
}
State transitions:
runApp() → loadingWidget → [Host.start()] → builder() + ServiceProviderScope
↓ (on error)
errorBuilder(error)
Declarative (HostProvider)
Fully declarative lifecycle management inside the widget tree:
void main() {
runApp(
HostProvider(
hostFactory: () async => await createDefaultBuilder().build(),
loadingBuilder: (_) => const MaterialApp(
home: Scaffold(body: Center(child: CircularProgressIndicator())),
),
errorBuilder: (_, error) => MaterialApp(
home: Scaffold(body: Center(child: Text('Error: $error'))),
),
child: const MyApp(),
),
);
}
HostProvider phases:
| Phase | Action |
|---|---|
initState |
Calls hostFactory(), then Host.start() |
| Build (loading) | Renders loadingBuilder (defaults to SizedBox.shrink) |
| Build (error) | Renders errorBuilder (defaults to red error text) |
| Build (ready) | Wraps child in ServiceProviderScope |
dispose |
Stops and disposes the host |
AppLifecycleState.detached |
Same shutdown as dispose |
Ecosystem integration
davianspace_hosting_flutter is the Flutter bridge for the DavianSpace ecosystem:
| Package | Role |
|---|---|
davianspace_configuration |
Hierarchical configuration (JSON, env vars, in-memory) |
davianspace_dependencyinjection |
Service collection & provider (singleton, scoped, transient) |
davianspace_logging |
Structured logging with providers and filtering |
davianspace_options |
Options pattern for strongly-typed settings |
davianspace_hosting |
Orchestrates all of the above |
davianspace_hosting_flutter |
Bridges the hosting runtime into Flutter's widget tree |
davianspace_http_resilience |
(optional) Retry, circuit breaker, timeout policies |
davianspace_http_ratelimit |
(optional) HTTP rate limiting |
Resolving services
Once a ServiceProviderScope is in the tree (provided automatically by
runFlutterApp, runFlutterAppAsync, or HostProvider), resolve services
from any descendant widget:
@override
Widget build(BuildContext context) {
// Required — throws if not registered
final auth = context.getService<AuthService>();
// Optional — returns null if not registered
final analytics = context.tryGetService<AnalyticsService>();
// All implementations of an interface
final handlers = context.getAllServices<EventHandler>();
// Keyed services
final primary = context.getKeyedService<Database>('primary');
final backup = context.tryGetKeyedService<Database>('backup');
// Raw provider access (advanced)
final sp = context.serviceProvider;
return Text(auth.currentUser);
}
Architecture
┌─────────────────────────────────────────────────────────────────┐
│ Flutter App │
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ ServiceProviderScope (InheritedWidget) │ │
│ │ provider: host.services │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ HostLifecycleObserver (WidgetsBindingObserver) │ │ │
│ │ │ • stops host on dispose │ │ │
│ │ │ • stops host on AppLifecycleState.detached │ │ │
│ │ │ │ │ │
│ │ │ ┌────────────────────────────────────────────────┐ │ │ │
│ │ │ │ Your Widget Tree │ │ │ │
│ │ │ │ context.getService<AuthService>() │ │ │ │
│ │ │ │ context.tryGetService<Analytics>() │ │ │ │
│ │ │ │ context.getKeyedService<Db>('primary') │ │ │ │
│ │ │ └────────────────────────────────────────────────┘ │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ davianspace_hosting (Host, HostedService, Lifetime) │ │
│ │ davianspace_dependencyinjection (ServiceProvider) │ │
│ │ davianspace_logging (LoggerFactory, Logger) │ │
│ │ davianspace_configuration (Configuration) │ │
│ └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
For a deep dive into internal design decisions, see doc/architecture.md.
Advanced usage
Keyed services
Disambiguate multiple implementations of the same type by key:
builder.configureServices((ctx, services) {
services
..addKeyedSingleton<Database>('primary', PrimaryDatabase())
..addKeyedSingleton<Database>('analytics', AnalyticsDatabase());
});
// In a widget:
final primary = context.getKeyedService<Database>('primary');
final analytics = context.tryGetKeyedService<Database>('analytics');
Multiple service implementations
Collect all implementations of an interface:
builder.configureServices((ctx, services) {
services
..addSingleton<EventHandler>(AuditHandler())
..addSingleton<EventHandler>(MetricsHandler());
});
// In a widget:
final handlers = context.getAllServices<EventHandler>();
for (final handler in handlers) {
handler.handle(event);
}
Custom loading and error UI
HostProvider(
hostFactory: buildHost,
loadingBuilder: (context) => const MaterialApp(
home: Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(),
SizedBox(height: 16),
Text('Initialising services…'),
],
),
),
),
),
errorBuilder: (context, error) => MaterialApp(
home: Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.error_outline, color: Colors.red, size: 48),
SizedBox(height: 16),
Text('Startup failed: $error'),
],
),
),
),
),
child: const MyApp(),
)
Programmatic shutdown from a widget
ElevatedButton(
onPressed: () {
final lifetime = context.getService<ApplicationLifetime>();
lifetime.requestShutdown();
},
child: const Text('Shutdown'),
)
Widget testing with DI
No mocking framework needed — the DI container itself serves as the test fixture:
testWidgets('displays greeting', (tester) async {
final services = ServiceCollection()
..addInstance<GreetingService>(const GreetingService('Hello'));
final provider = services.buildServiceProvider();
await tester.pumpWidget(
ServiceProviderScope(
provider: provider,
child: const MyGreetingWidget(),
),
);
expect(find.text('Hello'), findsOneWidget);
});
API reference
ServiceProviderScope
An InheritedWidget that exposes a ServiceProvider to the widget tree.
| Member | Description |
|---|---|
ServiceProviderScope.of(context) |
Returns the nearest scope; throws FlutterError with actionable guidance if none found |
ServiceProviderScope.maybeOf(context) |
Returns the nearest scope or null |
provider |
The ServiceProvider instance |
Rebuild behaviour: updateShouldNotify compares provider identity — since the provider is created once at host startup, this never triggers spurious descendant rebuilds.
BuildContext extensions (ServiceResolution)
Extension ServiceResolution on BuildContext:
| Method | Delegates to | Behaviour on missing registration |
|---|---|---|
getService<T>() |
ServiceProvider.getRequired<T>() |
Throws DI exception |
tryGetService<T>() |
ServiceProvider.tryGet<T>() |
Returns null |
getAllServices<T>() |
ServiceProvider.getAll<T>() |
Returns empty list |
getKeyedService<T>(key) |
ServiceProvider.getRequiredKeyed<T>(key) |
Throws DI exception |
tryGetKeyedService<T>(key) |
ServiceProvider.tryGetKeyed<T>(key) |
Returns null |
serviceProvider |
Direct getter | N/A |
All methods throw a FlutterError if no ServiceProviderScope is in the ancestor tree.
FlutterHostRunner
Extension FlutterHostRunner on Host:
| Method | Description |
|---|---|
runFlutterApp(builder) |
Starts host synchronously, wraps widget in scope, calls runApp |
runFlutterAppAsync(builder, {loadingWidget, errorBuilder}) |
Mounts immediately with loading UI, starts host in background |
Lifecycle guarantees:
- The host is started exactly once.
- Shutdown is triggered by either widget disposal or
ApplicationLifetime.requestShutdown. Host.stop()→Host.dispose()are called in sequence during teardown.AppLifecycleState.detachedalso triggers shutdown (defensive).
HostProvider
A StatefulWidget that manages the full Host lifecycle declaratively.
| Parameter | Type | Description |
|---|---|---|
hostFactory |
Future<Host> Function() |
Creates and returns the host (called once in initState) |
child |
Widget |
The application widget shown when the host is ready |
loadingBuilder |
WidgetBuilder? |
Optional builder shown during startup |
errorBuilder |
Widget Function(BuildContext, Object)? |
Optional builder shown on startup failure |
Lifecycle safety:
mountedis checked beforesetState— no errors during async gaps.- Shutdown is idempotent —
ApplicationLifetimeguards against double-shutdown. - Async operations are fire-and-forget in
dispose()(Flutter'sState.dispose()is synchronous).
Error handling
| Scenario | Behaviour |
|---|---|
Missing ServiceProviderScope |
FlutterError with actionable guidance (which widget to add and where) |
| Service not registered (required) | DI exception from getRequired<T>() |
| Service not registered (optional) | null from tryGet<T>() |
Host startup failure (runFlutterApp) |
Exception propagates to the caller; no widget tree mounted |
Host startup failure (runFlutterAppAsync) |
errorBuilder is called with the error |
Host startup failure (HostProvider) |
errorBuilder is called; defaults to red error text |
| Widget disposed during async startup | mounted check prevents setState on unmounted state |
| Double shutdown (dispose + detach) | Idempotent — ApplicationLifetime prevents double invocation |
Testing
Run the full test suite:
flutter test
Run with verbose output:
flutter test --reporter expanded
Run a specific test group:
flutter test --name "ServiceProviderScope"
The package includes 15 tests covering:
ServiceProviderScope— provider exposure,of()/maybeOf(), rebuild behaviourServiceResolution—getService,tryGetService,serviceProvidergetterHostProvider— loading/error/ready states, host start, host stop on disposeFlutterHostRunner—runFlutterApp,runFlutterAppAsync(loading gate, error)
Contributing
See CONTRIBUTING.md for development setup, coding guidelines, and the pull request process.
Security
See SECURITY.md for the vulnerability reporting process and supported versions.
License
MIT — see LICENSE for details.
Libraries
- davianspace_hosting_flutter
- Enterprise-grade Flutter integration for the DavianSpace hosting runtime.