inject_annotation 1.1.0-pre2 copy "inject_annotation: ^1.1.0-pre2" to clipboard
inject_annotation: ^1.1.0-pre2 copied to clipboard

Compile-time constructor-based dependency injection for Dart and Flutter, similar to Dagger.

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),
          ),
        );
      },
    );
  }
}
21
likes
0
points
906
downloads

Documentation

Documentation

Publisher

verified publisherdasralph.de

Weekly Downloads

Compile-time constructor-based dependency injection for Dart and Flutter, similar to Dagger.

Repository (GitHub)
View/report issues

Topics

#codegen #build-runner #dependency-injection #code-generation #generator

License

unknown (license)

Dependencies

meta

More

Packages that depend on inject_annotation