flutter_sputnik_di 0.2.1 copy "flutter_sputnik_di: ^0.2.1" to clipboard
flutter_sputnik_di: ^0.2.1 copied to clipboard

Lightweight Dependency Injection Framework for your Flutter project

Lightweight DI for your Dart project #

Getting started #

For a Dart project:

dart pub add sputnik_di

Or for a Flutter project:

dart pub add flutter_sputnik_di

Everything revolves around DepsNode (Dependency Node). A Dependency Node is an atomic unit of your code. It can serve as both a dependency container for your feature and as a scope for your application, such as AppScopeDepsNode, AuthScopeDepsNode, OrderScopeDepsNode, etc.

import 'package:flutter_sputnik_di/flutter_sputnik_di.dart';

Future<void> main() async {
  final featureDepsNode = FeatureDepsNode();

  await featureDepsNode.init();

  final featureManager = featureDepsNode.featureManager();

  // using featureManager
}

/// Dependency Node
class FeatureDepsNode extends DepsNode {
  @override
  @protected
  List<Set<LifecycleDependency>> initializeQueue = [
    {
      featureManager,
    },
  ];

  late final featureManager = bind(() => FeatureManager());
}

class FeatureManager implements Lifecycle {
  Future<void> init() {
    // ...
  }

  Future<void> dispose() {
    // ...
  }
}

All dependencies described in the dependency node must be wrapped in the bind method. This method creates a callback that, when calling a dependency, checks whether the current dependency node has been disposed of. Additionally, it acts as a wrapper for controlling calls in initializeQueue. This ensures that dependencies are not accessed before the node is initialized.

Of course, this does not completely eliminate the problem of early dependency calls, especially in production code where assert is used. However, this is a trade-off for ease of use and reducing the number of created entities.

Using with Flutter #

class FeatureWidget extends StatefulWidget {
  final Widget child;

  const FeatureWidget({
    required this.child,
    super.key
  });

  @override
  State<FeatureWidget> createState() => _FeatureWidgetState();
}

class _FeatureWidgetState extends State<FeatureWidget> {
  late final FeatureDepsNode featureDepsNode;

  @override
  void initState() {
    super.initState();

    featureDepsNode = FeatureDepsNode();
    unawaited(featureDepsNode.init());
  }

  @override
  void dispose() {
    unawaited(featureDepsNode.dispose());
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return DepsNodeBuilder(
      depsNode: featureDepsNode,
      initialized: (context, depsNode) {
        final featureManager = depsNode.featureManager();

        // using featureManager

        return DepsNodeBinder(
          depsNode: featureDepsNode,
          child: widget.child,
        );
      },
      orElse: (context, depsNode) {
        return Center(Text('Current depsNode status = ${depsNode.status}'));
      },
    );
  }
}

Built-in Simple State Management #

We are used to advanced state management systems, but they are not always convenient. In most cases, a simpler system is sufficient, and in sputnik_di, this model looks as follows:

class FeatureStateHolder extends StateHolder<String> {
  FeatureStateHolder() : super('DefaultValue');

  void updateState(String newState) {
    state = newState;
  }
}

class FeatureManager implements Lifecycle {
  final FeatureStateHolder _featureStateHolder;

// ...
}

class FeatureDepsNode extends DepsNode {
  @override
  @protected
  List<Set<LifecycleDependency>> initializeQueue = [];

  @override
  @protected
  List<Set<LifecycleDependency>> initializeQueue = [
    {
      /// Should be disposed after depsNode is disposed
      featureStateHolder,
    },
    {
      featureManager,
    },
  ];

  late final featureManager = bind(() => FeatureManager());

  late final featureStateHolder = bind(() => FeatureStateHolder());
}

class FeatureWidget extends StatelessWidget {
  const FeatureWidget({super.key});

  @override
  Widget build(BuildContext context) {
    final featureDepsNode = context.depsNode<FeatureDepsNode>();

    // rebuild on state change
    return StateHolderBuilder(
        holder: featureDepsNode.featureStateHolder(),
        builder: (context, state) {
          // listen to events from the state holder
          return StateHolderListener(
            listener: (data) {},
            holder: featureDepsNode.featureStateHolder(),
            child: const SizedBox.shrink(),
          );
        }
    );
  }
}

Mocking dependencies with overrideWith #

When writing tests or modifying dependencies dynamically, you can use the overrideWith method to replace an existing dependency with a mock implementation. This is useful for injecting test doubles, such as mocks or stubs, without modifying the original dependency registration.

Example:

void main() {
  final depsNode = FeatureDepsNode();

  // Override dependency with a mock
  depsNode.featureManager.overrideWith(() => MockFeatureManager());

  final featureManager = depsNode.featureManager();

  // Use featureManager in tests
}

Note: Overrides must be performed before initializing the DepsNode, ensuring the new dependency is used correctly.

Additional information #

The package includes a Lifecycle class, which is structured as follows:

abstract class Lifecycle {
  Future<void> init();

  Future<void> dispose();
}

It is used for classes that have a lifecycle. They can be registered in the dependency node via the initializeQueue getter.

Contributing and Support #

If you have any ideas, feature requests, or issues, feel free to open an issue or submit a pull request in the GitHub repository. Your feedback is highly appreciated!

If you like this project and want to support me, you can do so via Boosty. Every contribution helps keep this project alive and growing. Thank you! 😊

6
likes
160
points
50
downloads

Publisher

unverified uploader

Weekly Downloads

Lightweight Dependency Injection Framework for your Flutter project

Repository (GitHub)

Topics

#dependency-injection #inversion-of-control #ioc #container #utils

Documentation

API reference

Funding

Consider supporting this project:

boosty.to

License

BSD-3-Clause (license)

Dependencies

flutter, nested, sputnik_di

More

Packages that depend on flutter_sputnik_di