dependency_injector 0.1.0+2 copy "dependency_injector: ^0.1.0+2" to clipboard
dependency_injector: ^0.1.0+2 copied to clipboard

A dependency injection system for Flutter that automatically calls cancel, close and dispose methods, if any.

Dependency Injector #

A dependency injection system for Flutter that automatically calls cancel, close and dispose methods, if any.

See example for details, and run it!

Getting Started #

Configure the services you want to use in your application.

final services = [
  Transient(() => SomeService()),
  Singleton(() => Repository()),
];

Place the RootInjector with your services list in the root of your application, and wrap the widget where you want to inject the service in the Injector.

void main() {
  runApp(RootInjector(
    services: services,
    child: Injector(() => MyApp()),
  ));
}

Inject your service using the inject property.

class MyApp extends StatelessWidget {
  MyApp({Key key}) : super(key: key);

  final SomeService someService = inject();

  ...
}

Complete example.

import 'package:flutter/material.dart';
import 'package:dependency_injector/dependency_injector.dart';

class SomeService {
  final Repository repository = inject();
}

class Repository {
  String getData() => 'Hello world.';
}

final services = [
  Transient(() => SomeService()),
  Singleton(() => Repository()),
];

void main() {
  runApp(RootInjector(
    services: services,
    child: Injector(() => MyApp()),
  ));
}

class MyApp extends StatelessWidget {
  MyApp({Key key}) : super(key: key);

  final SomeService someService = inject();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: Scaffold(
        body: Center(child: Text(someService.repository.getData())),
      ),
    );
  }
}

Services #

There are three types of services: Singleton, Scoped, Transient. They all use lazy initialization.

Singleton #

They are created only once on the first injection and exist as long as the ancestor RootInjector exists. Singleton services can contain other singleton or transient services, but they must not contain any scoped services as descendants. Singleton services never call cancel, close and dispose methods.

Singleton(() => SomeService()),

Scoped #

Scoped services are created once for the their parent Injector and exist as long as this injector exists, when they are injected again to descendants down the tree, the already created instance is taken. They can contain any other types of services as descendants. Scoped services call cancel, close and dispose methods when their parent Injector (for which they were created) is removed from the tree.

Scoped(() => SomeService()),

Transient #

When transient services are used directly in the tree or as descendants of other transient services that are used directly in the tree, they are created once for their parent Injector and exist as long as this injector exists, when they are injected again into other injectors that are descendants down the tree, a new instance will be created. If they are descendants of singleton or scoped services, then their life cycle is the same as for singleton or scoped services. Transient services call cancel, close and dispose methods when their parent Injector (for which they were created) is removed from the tree, or according to the life cycle of of singleton or scoped services if they are descendants of them.

Transient(() => SomeService()),

Parameters #

All three types of services have parameterized versions ParameterizedSingleton, ParameterizedScoped, ParameterizedTransient, their life cycle is the same as the regular versions. For example, if you have injected the same ParameterizedTransient twice into the same Injector, then the second injection will take an existing instance.

ParameterizedTransient<SomeService, String>(
  (p) => SomeService(p),
),

Dispose #

Scoped and Transient (only if Transient services are not descendants of singletons) services, by default, call cancel, close and dispose methods when their life circle ends. They call these methods only if these methods do not have any parameters. If you need to call them with parameters, you have to provide custom disposer, it will override the default disposer.

Scoped<SomeService>(
  () => SomeService(),
  disposer: (instance) async {
    instance.dispose('some data');
  },
),

If you just want to disable a default disposer, set useDefaultDisposer to false.

Transient(
  () => SomeService(),
  useDefaultDisposer: false,
),

Keys #

When configuring services, you cannot use the same type more than once.

final services = [
  Transient(() => 1),
  Singleton(() => 2), // There will be an error.
];

In these cases you have to use a key!

class ServiceKey extends ServiceKeyBase {
  const ServiceKey._(String name) : super(name);

  static const someKey = ServiceKey._('someKey');
}

final services = [
  Transient(() => 1),
  Singleton(() => 2, key: ServiceKey.someKey), // Ok.
];

And then another service of this type will be configured.

Injectors #

There are two types of injectors: RootInjector and Injector, and inject property.

RootInjector #

Used to configure services. It should only be used once at the root of your application.

void main() {
  runApp(RootInjector(
    services: services,
    child: MyApp(),
  ));
}

Injector #

Used as a wrapper for the widget into which you will inject dependencies. You should consider the Injector and it's widget as a whole.

Injector(() => SomeWidget())

Never put any logic like conditional statements or expressions inside an injector's builder method. In some cases, this can be the cause of the error, because the injector creates all the instances that you inject only once!

Injector(() => value == 7 ? SomeWidget1() : SomeWidget2()) // Bad.

If you need do this use this pattern.

value == 7 ? Injector(() => SomeWidget1()) : Injector(() => SomeWidget2()) // Good.

Or with keys.

value == 7 
  ? Injector(() => SomeWidget2(), key: ValueKey(1))
  : Injector(() => SomeWidget2(), key: ValueKey(2)) // Good.

Inject #

This is the property you use for dependency injection. It available only while builder method works.

For widgets:

class SomeWidget extends StatelessWidget {
  SomeWidget({Key key}) : super(key: key);

  final SomeService someService = inject();

  ...
}

...

Injector(() => SomeWidget())
class SomeWidget extends StatelessWidget {
  const SomeWidget(this.someService, {Key key}) : super(key: key);

  final SomeService someService;

  ...
}

...

Injector(() => SomeWidget(inject()))

For services:

class SomeService1 {
  final SomeService2 someService2 = inject();
}

final services = [
  Singleton(() => SomeService1()),
];
class SomeService1 {
  SomeService1(this.someService2);

  final SomeService2 someService2;
}

final services = [
  Singleton(() => SomeService1(inject())),
];

When you need inject a service with a key.

class SomeService1 {
  final SomeService2 someService21 = inject();
  final SomeService2 someService22 = inject(key: ServiceKey.someKey);
}

When you need inject a service with parameters.

class SomeService1 {
  final SomeService2 someService2 = inject(parameters: 'Some parameter');
}

If you want to get another one instance of the same service, you can use ServiceIndex. All services have a zero index by default.

class SomeService1 {
  final SomeService2 someService21 = inject(parameters: 1); // someService21.value == 1
  final SomeService2 someService22 = inject(parameters: 2); // someService22.value == 1
  final SomeService2 someService23 = inject(
    parameters: 3,
    index: ServiceIndex.zero,
  ); // someService23.value == 1
  final SomeService2 someService24 = inject(
    parameters: 4,
    index: ServiceIndex.one,
  ); // someService24.value == 4
}

Testing #

For unit tests you should use RootInjectorForTest to make inject property available.

void main() {
  RootInjectorForTest(services);
  test('Some Unit Test', () {
    final SomeService someService = inject();

    ...

  });

For widget tests do this

void main() {
  testWidgets('Some Widget Test', (tester) async {
    await tester.pumpWidget(
      RootInjector(
        services: services,
        child: MaterialApp(
          home: Scaffold(
            body: Injector(() => SomeWidget()),
          ),
        ),
      ),
    );

    ...

  });
}

If you need to replace a service with mock you may use replaceWithMock method.


class MockSomeService extends Mock implements SomeService {}

void main() {
  final newServices = replaceWithMock(
    services: services,
    mockServices: [
      ParameterizedScoped<SomeService, int>((p) {
        final mockSomeService = MockSomeService();

        when(mockSomeService.value).thenReturn(100500);

        return mockSomeService;
      }),
    ],
  );

  ...

}
6
likes
40
pub points
0%
popularity

Publisher

unverified uploader

A dependency injection system for Flutter that automatically calls cancel, close and dispose methods, if any.

Repository (GitHub)
View/report issues

License

BSD-3-Clause (LICENSE)

Dependencies

flutter, meta

More

Packages that depend on dependency_injector