needle 0.0.1-dev.2

  • Readme
  • Changelog
  • Example
  • Installing
  • new47

Needle #

Needle is a dependency injection library for Dart/Flutter inspired by AutoFac (https://autofac.org/). It uses a generated reflection mechanism to construct registered class by walking their dependency trees and creating their dependents.

For example, in order to create an instance of SalesAccountBloc, the constructor must be provided with instances of AgentRepository and CustomerRepository.

class SalesAccountBloc {
  SalesAccountBloc({
    this.agentRepository,
    this.customerRepository,
  });

  final AgentRepository agentRepository;
  final CustomerRepository customerRepository;
}

Needle uses the generated reflection data to examine the parameters of the SalesAccountBloc constructor and will see that it requires an instance of AgentRepository and CustomerRepository. Needle will then recursively resolve AgentRepository and CustomerRepository to create instances to pass to the SalesAccountBloc constructor.

When it attempts to resolve and create an instance of AgentRepository, it will see that its constructor requires an instance of AgentDataStore and ObjectCache. It will then recursively resolve those two objects.

class AgentRepository {
  AgentRepository({
    this.dataStore,
    this.objectCache,
  });

  final AgentDataStore dataStore;
  final ObjectCache objectCache;
}

Needle uses type registrations to provide information about how to create instances of types, how the object will be cached, and what interface or base class will be used to reference the type. For example, the following registers the AgentRepositoryImpl class as the class to be resolved when the AgentRepository interface is requested. Additionally, it specifies that the class will be a singleton.

builder.registerType<AgentRepositoryImpl>
  .as<AgentRepository>()
  .singleInstance();

Similarly, the following registers the ObjectCache class and specifies that a new instance of the class should be created each time the class is resolved.

builder.registerType<AgentRepositoryImpl>
  .instancePerDependency();

Registrations can also be named, allow multiple registrations to be created for a type that can be specified by constructor parameters using the @Named annotation.

builder.registerType<AgentObjectCache>
  .as<ObjectCache>
  .withName('Agent')
  .singleInstance();

builder.registerType<AgentObjectCache>
  .as<ObjectCache>
  .withName('Customer')
  .singleInstance();
class AgentRepository {
  AgentRepository({
    this.dataStore,
    @Named('Agent') this.objectCache,
  });

  final AgentDataStore dataStore;
  final ObjectCache objectCache;
}

Type registrations can specify other attributes, such as the name of the constructor to use and a list of parameter values to supply to the constructor.

builder.registerType<ObjectCache>
  .withConstructor('fixedSize')
  .withParameters({'size': 10})
  .instancePerDependency();

Getting Started #

Needle uses reflection to walk the dependencies of classes. Dart provides a reflection mechanism, however it is not enabled in the Flutter SDK due to performance concerns and because it would prevent the use of Flutter's tree-shaking mechanism that reduces the size of apps.

Needle overcomes this by using code generation to create ClassMirror instances for classes that have been marked by the @reflect or @ReflectInclude annotations. The ClassMirror objects provide the registration with information about the constructors for the class, their parameters, and a function that invokes the constructor.

The ContainerBuidler class is used to create registrations and build the root Scope object that is used to resolve classes. ContainerBuilder is an class that must be subclassed to provide a getMirror() method that the ContainerBuilder uses to retrieve the ClassMirror for a registration. While this could be done manually, Needle provides a code generation builder that will generate the ClassMirror objects automatically.

To use the code generation, create a class definition and annotate it with the @needle annotation.

import 'injection_builder.needle.dart';

@needle
class InjectionBuilder extends $InjectionBuilder {}

The class must extend a class whose name is the name of the builder class prefixed with a $. This class will be created during the code generat step. Additionally, the file must import a file whose name is the same as the source file, but with an .needle.dart extension.

The .needle.dart file is created by the code generator and contains the base class for the builder. This class extends the ContainerBuilder class and provides an implementation of the getMirror() method that will return ClassMirror objects for each class annotated with the @reflect annotation.

@reflect
class AgentRepository {
  AgentRepository({
    this.dataStore,
    this.objectCache,
  });

  final AgentDataStore dataStore;
  final ObjectCache objectCache;
}

When annotating a class with @reflect is undesirable or not feasible, such as with classes from a third party library, the @ReflectInclude annotation can be applied to the builder class.

@ReflectInclude(SomeService)
@needle
class InjectionBuilder extends $InjectionBuilder {}

The root Scope object is created by creating an instance of the builder class, registering types with it, and then calling the build() method.

final builder = InjectionBuilder();

builder.registerType<ObjectCache>.instancePerDependency();
builder.registerType<AgentDataStore>.singleInstance();
builder.registerType<AgentRepository>.singleInstance();

final scope = builder.build();

final repository = scope.resolve<AgentRepository>();

0.0.1-dev.1 #

Initial release

0.0.1-dev.2 #

Refactor and documentation

example/lib/main.dart

import 'dart:math';

import 'package:needle/needle.dart';

import 'main.needle.dart';

@ReflectInclude(AnotherService)
@needle
class Builder extends $Builder {}

void main() {
  final builder = Builder();
  builder.setLogger((message) => print(message));

  builder.registerInstance(SomeService(5));

  builder.registerFactory((container) => AnotherService(
        someField: 5,
        anotherField: 6,
      ));

  builder.registerType<FooRepository>().singleInstance();

  builder
      .registerType<FooDataStore>()
      .withConstructor('large')
      .singleInstance();

  builder
      .registerType<BarDataStore>()
      .singleInstance()
      .withParameters({'size': 6});

  builder.registerType<BarRepository>().singleInstance();

  builder
      .registerType<RepositoryModelImpl>()
      .as<RepositoryModel>()
      .singleInstance()
      .withParameters(
          {'fooCache': const Named('Foo'), 'barCache': const Named('Bar')});

  builder.registerType<ObjectCache>().singleInstance().withName('Foo');
  builder.registerType<ObjectCache>().singleInstance().withName('Bar');

  final container = builder.build();

  final repositoryModel = container.resolve<RepositoryModel>();

  final barCache = container.resolve<ObjectCache>(name: 'Bar');
  final fooCache = container.resolve<ObjectCache>(name: 'Foo');

  final repositoryModelCopy = container.resolve<RepositoryModel>();
}

@reflect
class ObjectCache {
  final cache = <dynamic>[];
  final id = Random().nextInt(1000);
}

@reflect
class BarDataStore {
  BarDataStore(@Named('Bar') this.cache, this.size);

  final ObjectCache cache;
  final int size;
}

@reflect
class BarRepository {
  BarRepository(this._dataStore);

  final BarDataStore _dataStore;

  void test() {
    print(_dataStore.size);
  }
}

@reflect
class FooDataStore {
  FooDataStore(@Named('Foo') this.cache, this.size);
  FooDataStore.large(@Named('Foo') this.cache) : size = 10;
  FooDataStore.small(@Named('Foo') this.cache) : size = 2;

  final ObjectCache cache;
  final int size;
}

@reflect
class FooRepository {
  FooRepository(this._dataStore);

  final FooDataStore _dataStore;

  void test() {
    print(_dataStore.size);
  }
}

abstract class RepositoryModel {}

@reflect
class RepositoryModelImpl implements RepositoryModel {
  RepositoryModelImpl(
      {this.fooRepository, this.barRepository, this.fooCache, this.barCache});

  final FooRepository fooRepository;
  final BarRepository barRepository;
  final ObjectCache fooCache;
  final ObjectCache barCache;
}

@reflect
class SomeService {
  SomeService(this.someField);

  final int someField;
}

class AnotherService {
  AnotherService({this.someField, this.anotherField});

  final int someField;
  final int anotherField;
}

class AgentDataStore {
  AgentDataStore();
}

class AgentRepository {
  AgentRepository({
    this.dataStore,
    this.objectCache,
  });

  final AgentDataStore dataStore;
  final ObjectCache objectCache;
}

class CustomerDataStore {
  CustomerDataStore();
}

class CustomerRepository {
  CustomerRepository({
    this.dataStore,
    this.objectCache,
  });

  final CustomerDataStore dataStore;
  final ObjectCache objectCache;
}

class SalesAccountBloc {
  SalesAccountBloc({
    this.agentRepository,
    this.customerRepository,
  });

  final AgentRepository agentRepository;
  final CustomerRepository customerRepository;
}

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  needle: ^0.0.1-dev.2

2. Install it

You can install packages from the command line:

with pub:


$ pub get

with Flutter:


$ flutter pub get

Alternatively, your editor might support pub get or flutter pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:needle/needle.dart';
  
Popularity:
Describes how popular the package is relative to other packages. [more]
0
Health:
Code health derived from static analysis. [more]
100
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
85
Overall:
Weighted score of the above. [more]
47
Learn more about scoring.

We analyzed this package on Jul 4, 2020, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.8.4
  • pana: 0.13.13

Maintenance suggestions

Package is pre-v0.1 release. (-10 points)

While nothing is inherently wrong with versions of 0.0.*, it might mean that the author is still experimenting with the general direction of the API.

Package is pre-release. (-5 points)

Pre-release versions should be used with caution; their API can change in breaking ways.

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.1.0 <3.0.0
analyzer ^0.39.10 0.39.11
build ^1.3.0 1.3.0
dart_style ^1.3.6 1.3.6
freezed_annotation ^0.11.0 0.11.0
glob ^1.2.0 1.2.0
source_gen ^0.9.5 0.9.5
Transitive dependencies
_fe_analyzer_shared 5.0.0
args 1.6.0
async 2.4.1
charcode 1.1.3
collection 1.14.13
convert 2.1.1
crypto 2.1.5
csslib 0.16.1
html 0.14.0+3
js 0.6.2
json_annotation 3.0.1
logging 0.11.4
meta 1.1.8
node_interop 1.1.1
node_io 1.1.1
package_config 1.9.3
path 1.7.0
pedantic 1.9.0 1.9.1
pub_semver 1.4.4
source_span 1.7.0
string_scanner 1.0.5
term_glyph 1.1.0
typed_data 1.2.0
watcher 0.9.7+15
yaml 2.2.1
Dev dependencies
build_runner ^1.10.0
build_test ^1.0.0
freezed ^0.11.2