inject_generator 1.1.0-pre2
inject_generator: ^1.1.0-pre2 copied to clipboard
Dev-time dependency for package:inject_annotation.
example/lib/main.dart
// The pub.dev showcase for `inject.dart` + `inject_flutter`.
//
// The example walks through every concept a Flutter developer typically
// needs:
//
// 1. Defining the dependency graph with `@Component`.
// 2. Registering external bindings via an `@module`.
// 3. Hooking into provisioning with `@provisionListener` (see issue #36).
// 4. Building widgets with assisted injection (`@assistedInject`).
// 5. Bridging Flutter's widget lifecycle with `ViewModelFactory`.
import 'package:flutter/material.dart';
import 'package:inject_annotation/inject_annotation.dart';
import 'package:inject_flutter/inject_flutter.dart';
import 'main.inject.dart' as g;
part 'main.factory.dart';
/// Entry point of the example app.
///
/// Builds the root [AppComponent], asks it for an [ExampleAppFactory], and
/// hands the resulting widget tree to [runApp].
void main() {
final mainComponent = AppComponent.create();
final app = mainComponent.exampleAppFactory.create();
// The [CreationLogListener] was wired up by [AppModule]'s
// `@provisionListener` binding and will log every provisioned
// [ChangeNotifier] to the debug console from now on. Pull it out of
// the component whenever you want to read its state, reset it, or hook
// in your own logic — do something awesome with it!
// ignore: unused_local_variable
final creationLogListener = mainComponent.creationLogListener;
runApp(app);
}
/// Root of the dependency graph.
///
/// `@Component([AppModule])` tells the generator to emit a concrete
/// implementation called `AppComponent$Component` in `main.inject.dart`,
/// wired against the providers declared in [AppModule]. The static [create]
/// alias hides the generated class name from call sites in [main].
@Component([AppModule])
abstract class AppComponent {
/// Convenience accessor for `AppComponent$Component.create`.
static const create = g.AppComponent$Component.create;
/// Factory for the root [ExampleApp] widget.
///
/// Returning a factory (instead of an `ExampleApp` directly) lets the
/// caller pass a `Key?` without having to know about inject.dart-managed
/// dependencies — that is the essence of assisted injection.
@inject
ExampleAppFactory get exampleAppFactory;
/// Exposes the singleton [CreationLogListener] so [main] — or any other
/// part of the app — can read its state on demand.
///
/// Surfacing a [ProvisionListener] on the component itself is optional;
/// the listener works regardless. It is exposed here purely to make the
/// `@provisionListener` flow visible end-to-end in the showcase.
@inject
CreationLogListener get creationLogListener;
}
/// Hosts the application's external bindings.
///
/// A `@module` is a class whose `@provides` methods contribute bindings to
/// the dependency graph. Use it for types that cannot simply be annotated
/// with `@inject` themselves — for example third-party classes, types that
/// need custom construction, or — as shown here — a [ProvisionListener]
/// that observes the graph at runtime.
@module
class AppModule {
/// Provides the singleton [CreationLogListener] and registers it as a
/// provisioning hook.
///
/// The combination of annotations is what makes this binding special:
///
/// - `@provides` exposes the return value to the dependency graph.
/// - `@singleton` guarantees a single shared instance — required for any
/// listener that wants to observe *every* provisioning.
/// - `@provisionListener` tells the generator to invoke `onProvision`
/// after each dependency of the listener's generic type is created.
/// Here the type argument is [ChangeNotifier], so the listener fires
/// for [HomePageViewModel] (which extends [ChangeNotifier]) but not
/// for [ExampleApp] or [HomePage].
@provides
@singleton
@provisionListener
CreationLogListener provideCreationLogListener() => CreationLogListener();
}
/// A type-specific [ProvisionListener] that logs every [ChangeNotifier]
/// the component provisions and keeps a running count.
///
/// This is the pattern requested in
/// [issue #36](https://github.com/ralph-bergmann/inject.dart/issues/36) —
/// modelled after Guice's `ProvisionListener` API. The listener is a pure
/// observer: it does not hold references to the provisioned instances, so
/// it does not interfere with anyone else managing their lifecycle (for
/// example [ViewModelFactory], which already disposes view models when
/// their widget leaves the tree).
///
/// Typical real-world uses:
///
/// - centralised logging or metrics on dependency creation;
/// - tracking `Closeable`-like resources (DB pools, sockets) so a test
/// teardown can release them in one call;
/// - lifecycle hooks for non-Flutter objects that no framework manages
/// automatically.
class CreationLogListener implements ProvisionListener<ChangeNotifier> {
int _provisionCount = 0;
/// Number of [ChangeNotifier] instances provisioned since startup.
int get provisionCount => _provisionCount;
@override
void onProvision(ChangeNotifier instance) {
_provisionCount++;
debugPrint('inject.dart -> provisioned ${instance.runtimeType} (#$_provisionCount)');
}
}
/// The top-level [MaterialApp] widget.
///
/// Constructed via [ExampleAppFactory] so inject.dart can supply
/// [homePageFactory] automatically while leaving `Key?` to the caller.
class ExampleApp extends StatelessWidget {
@assistedInject
const ExampleApp({@assisted super.key, required this.homePageFactory});
/// Factory used to build the [HomePage] widget on demand.
final HomePageFactory homePageFactory;
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: homePageFactory.create(title: 'Flutter Demo Home Page'),
);
}
}
/// View model backing [HomePage].
///
/// Annotated with `@inject` so inject.dart can construct it as part of the
/// dependency graph. Because it extends [ChangeNotifier], every newly
/// provisioned instance also flows through the [CreationLogListener]
/// registered in [AppModule].
@inject
class HomePageViewModel extends ChangeNotifier {
/// Number of times the floating action button has been pressed.
int count = 0;
/// One-shot initialiser, invoked via [ViewModelFactory]'s `init`
/// callback. Simulates loading an initial counter value from a data
/// source — the counter starts at `5` instead of `0`.
void init() {
count = 5;
notifyListeners();
}
/// Increments [count] and notifies listeners so the UI rebuilds.
void incrementCounter() {
count++;
notifyListeners();
}
}
/// Counter page wired up via [ViewModelFactory].
///
/// The factory is the bridge between inject.dart and Flutter's widget
/// lifecycle: it creates a fresh [HomePageViewModel] in `initState`,
/// rebuilds the subtree whenever the view model notifies, and calls
/// `dispose()` on the view model in `State.dispose`.
class HomePage extends StatelessWidget {
@assistedInject
const HomePage({
@assisted super.key,
@assisted required this.title,
required this.viewModelFactory,
});
/// Title shown in the [AppBar].
final String title;
/// Factory for the [HomePageViewModel].
///
/// Owning the factory instead of the view model itself keeps [HomePage]
/// a [StatelessWidget] — the generated `ViewModelBuilder` handles the
/// stateful parts and the lifecycle.
final ViewModelFactory<HomePageViewModel> viewModelFactory;
@override
Widget build(BuildContext context) {
return viewModelFactory(
init: (viewModel) => viewModel.init(),
builder: (context, viewModel, _) {
return Scaffold(
appBar: AppBar(title: Text(title)),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('You have pushed the button this many times:'),
Text('${viewModel.count}', style: Theme.of(context).textTheme.headlineMedium),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: viewModel.incrementCounter,
child: const Icon(Icons.add),
),
);
},
);
}
}