df_di 0.9.3 copy "df_di: ^0.9.3" to clipboard
df_di: ^0.9.3 copied to clipboard

A pragmatic dependency injection (DI) system, coupled with service classes for seamless state management in Dart.

Buy Me A Coffee

Dart & Flutter Packages by DevCetra.com & contributors.

Pub Package MIT License


Summary #

This package provides a pragmatic dependency injection (DI) framework, coupled with service classes for seamless state management in Dart, as well as a new ECS (Entity Component System) build on top of the DI framework.

Features #

  • Robust FutureOr support for handling both synchronous and asynchronous dependencies and callbacks.
  • Register dependencies by type and groupEntity, enabling management of multiple dependencies of the same type.
  • Hierarchical DI with scoped dependencies through child containers.
  • Retrieve dependencies by runtime type or generic type.
  • Factory dependencies and lazy initialization for singleton dependencies.
  • Service classes that automatically handle cleanup when they are unregistered.
  • Extension of the DI system into an ECS framework called “World.” For an example, see: https://github.com/DevCetra/df_di/blob/main/tests/world.dart

For a full feature set, please refer to the API reference.

Quickstart #

Creating a DI container: #

// Access the global DI instance anywhere in your app.
DI.global;

// Or create your own DI container.
final di = DI();

// Create nested child containers, useful for scoping dependencies in modular apps.
final scopedDi = di.child().child().child(groupEntity: Entity('moduleGroup'));

Registering Dependencies: #

// Register the answer to life, the universe and everything.
di.register<int>(42);

// Register an integer under a specific groupEntity, useful in environments like testing.
di.register<int>(0, groupEntity: Entity.testGroup);

// Register a Future as a dependency.
di.register(Future.value('Hello, DI!'));
print(di.get<String>()); // Instance of 'Future<String>'

// Register a factory or lazy singleton constructor.
int n = 0;
di.registerLazy<int>(() => n + 1);
di.registerLazy(() => DateTime.now());

Unregistering Dependencies: #

// Unregister a specific type.
  di.unregister<int>();
  di.unregisterT(int);

// Unregister all dependencies, resetting the container.
  di.unregisterAll();

// Unregister child containers when they’re no longer needed.
  di.unregisterChild();

Getting Dependencies: #

// Retrieve a registered integer dependency.
print(di<int>()); // 42
Type intType = int;
print(di.getT(intType)); // 42

// Retrieve a dependency registered under a specific groupEntity.
print(di.get<int>(groupEntity: Entity('testGroup'))); // 0

// Handle asynchronous dependencies.
final greeting = await di.get<String>();
print(greeting); // Hello, DI!

// Retrieve a factory-registered dependency.
final now = di.getFactory<DateTime>();
print(now); // Current timestamp
await Future.delayed(Duration(seconds: 1));
final now1 = di.getFactoryT(DateTime);
print(now1);  // A second later

Real-World Example - UserService: #

final class UserService extends Service {
  final _userName = ValueNotifier<String>('Guest');

  // Getter for the UI to consume.
  ValueListenable<String> get userName => _userName;

  @override
  Future<void> onInitService(_) async {
    // Simulate loading user data.
    await Future.delayed(Duration(seconds: 2));
    _userName.value = 'John Doe';
  }

  @override
  void onDispose() {
    _userName.dispose(); // Cleanup resources.
  }
}

// Register the service.
di.registerService(UserService.new);

// Access the service.
final userService = await di.getServiceSingleton<UserService>();
print(userService.userName.value); // John Doe

Handling Synchronous and Asynchronous Services: #

Service with Synchronous Initialization and Asynchronous Disposal

final class SyncInitAsyncDisposeService extends Service {
  // di<SyncInitAsyncDisposeService>() will not return a Future.
  @override
  void onInitService(_) {
    // Synchronous initialization logic
  }

  // di.unregister<SyncInitAsyncDisposeService>() will return a Future.
  @override
  Future<void> onDispose() async {
    // Asynchronous cleanup logic
  }
}

// Register and use the service.
di.registerService(SyncInitAsyncDisposeService.new);
final service = di.getServiceSingleton<SyncInitAsyncDisposeService>();
await di.unregister<SyncInitAsyncDisposeService>();

Service with Asynchronous Initialization and Synchronous Disposal

final class AsyncInitSyncDisposeService extends Service {
  // di<AsyncInitSyncDisposeService>() will not return a Future.
  @override
  Future<void> onInitService(_) async {
    await Future.delayed(Duration(seconds: 3));
    // Asynchronous initialization logic
  }

  // di.unregister<AsyncInitSyncDisposeService>() will not return a Future.
  @override
  void onDispose() {
    // Synchronous cleanup logic
  }
}

// Register and use the service.
di.registerService(AsyncInitSyncDisposeService.new);
final service = await di.getServiceSingleton<AsyncInitSyncDisposeService>();
di.unregister<AsyncInitSyncDisposeService>();

Getting the State for Debugging: #

// Print the current state of the DI container.
print(di.registry.state);

// Check if a specific type is registered.
print(di.isRegistered<int>()); // true

Implementing an Entity Component System (ECS) using the DI Framework: #

This is especially useful for game development with Flutter but can also be applied to app development, such as managing complex forms with dynamic field visibility or handling multiple user profiles in a customizable app.

Create various components that can be applied to entities.

class Name extends Component {
  final String name;
  const Name(this.name);

  @override
  List<Object?> get props => [name];
}

class Vector extends Component {
  final double x;
  final double y;
  const Vector({
    this.x = 0.0,
    this.y = 0.0,
  });

  Vector add(Vector other) {
    return Vector(
      x: x + other.x,
      y: y + other.y,
    );
  }

  @override
  List<Object?> get props => [x, y];
}

class Position extends Vector {
  const Position({
    super.x = 0.0,
    super.y = 0.0,
  });

  @override
  Position add(Vector other) {
    return Position(
      x: x + other.x,
      y: y + other.y,
    );
  }
}

class Velocity extends Vector {
  const Velocity({
    super.x = 0.0,
    super.y = 0.0,
  });

  @override
  Velocity add(Vector other) {
    return Velocity(
      x: x + other.x,
      y: y + other.y,
    );
  }
}

Create a movement system to update entities in the world.

class MovementSystem extends UpdateSystem {
  @override
  void update(World world) {
    // Get all entities with both Position and Velocity components.
    final entities = world.query2<Position, Velocity>();
    for (var entity in entities) {
      // Update the position based on the velocity
      final Position position = entity.getComponent();
      final Velocity velocity = entity.getComponent();
      final result = world.updateComponent(
        entity,
        position.add(velocity),
      );
      final newPosition = result.unwrap() as Position;

      // Log the updated position.
      print('Updated Position: (${newPosition.x}, ${newPosition.y})');
    }
  }
}

abstract class UpdateSystem {
  void update(World world);
}

Move a player in the world.

// Create a new world for players or users to exist in.
final world = World();

// Create a new player in the world.
final player1 = world.createUniqueEntity();

// Spawn the player in the world with the components Name, Position and Velocity.
world.addAllComponents(player1, {
  const Name('Player 1'),
  const Position(x: 0, y: 0),
  const Velocity(x: 1, y: 0),
});

// Print the current position.
final p0 = player1.getComponent<Position>();
print('Position 0: (${p0.x}, ${p0.y})');

// Update the movement in the world.
final movementSystem = MovementSystem();
movementSystem.update(world);

// Print the position after the world update.
final p1 = player1.getComponent<Position>();
print('Position 1: (${p1.x}, ${p1.y})');

// Print the player name.
final name = player1.getComponent<Name>().name;
print('Player name: "$name"');

Installation #

Use this package as a dependency by adding it to your pubspec.yaml file (see here).


Contributing and Discussions #

This is an open-source project, and we warmly welcome contributions from everyone, regardless of experience level. Whether you're a seasoned developer or just starting out, contributing to this project is a fantastic way to learn, share your knowledge, and make a meaningful impact on the community.

Ways you can contribute: #

  • Buy me a coffee: If you'd like to support the project financially, consider buying me a coffee. Your support helps cover the costs of development and keeps the project growing.
  • Share your ideas: Every perspective matters, and your ideas can spark innovation.
  • Report bugs: Help us identify and fix issues to make the project more robust.
  • Suggest improvements or new features: Your ideas can help shape the future of the project.
  • Help clarify documentation: Good documentation is groupEntity to accessibility. You can make it easier for others to get started by improving or expanding our documentation.
  • Write articles: Share your knowledge by writing tutorials, guides, or blog posts about your experiences with the project. It's a great way to contribute and help others learn.

No matter how you choose to contribute, your involvement is greatly appreciated and valued!


Chief Maintainer: #

📧 Email Robert Mollentze at robmllze@gmail.com

Dontations: #

If you're enjoying this package and find it valuable, consider showing your appreciation with a small donation. Every bit helps in supporting future development. You can donate here:

https://www.buymeacoffee.com/robmllze


License #

This project is released under the MIT License. See LICENSE for more information.

1
likes
160
pub points
61%
popularity

Publisher

verified publisherdevcetra.com

A pragmatic dependency injection (DI) system, coupled with service classes for seamless state management in Dart.

Repository (GitHub)
View/report issues

Topics

#get-it #dependency-injection #services #service-locator #ecs

Documentation

API reference

Funding

Consider supporting this project:

www.buymeacoffee.com

License

MIT (license)

Dependencies

collection, df_debouncer, df_type, equatable, meta

More

Packages that depend on df_di