Toni Joe
Toni Joe

Reputation: 8417

How to implement dependency injection in Flutter manually?

Since there is no official library from the flutter team, I'm trying to implement the dependency injection in flutter manually using the singleton pattern, after a long search this is what I came up with:

class Injector{
    Injector._internal();
    static final _singleton = new Injector._internal();
    factory Injector() => _singleton;
    SomeClass get someClass => new SomeClass();
}

Now, Injector is singleton that has one instance once instantiated and SomeClass is the dependency I want to inject in my code. The above code works, but the problem is where should I instantiate the Injector class and make it available every where in my code. Do you think Global Variable is good in this situation or is there a better way? Thanks.

Upvotes: 3

Views: 1855

Answers (3)

Max
Max

Reputation: 93

Clear example, fulfils all needs:

/// For use
abstract class Database {}

/// One of the implementations
class LocalDatabase extends Database {}

/// Lazy initialization factory
typedef Factory<T> = T Function();

/// Objects store
class InjectFactory<T> {
  final Factory<T> factory;
  T? _object;
  InjectFactory(this.factory);
  T get object => _object ?? (_object = factory());
}

/// Dependencies store
final store = HashMap<Type, InjectFactory>();

/// Methods
void register<T>(Factory<T> factory) => store[T] = InjectFactory<T>(factory);
T? get<T>() => store[T]?.object as T?;

/// Example
void main() {
  test('Test', () {
    register<Database>(() => LocalDatabase());
    Database? db = get<Database>();
    expect(db is Database, true);
    expect(db is LocalDatabase, true);
    expect(db.runtimeType, LocalDatabase);
  });
}

Upvotes: 0

Toni Joe
Toni Joe

Reputation: 8417

This is my solution for this problem. First I created a dart file named injector.dart with this code:

// the singleton is private to this package
final _injector = new _Injector();
// expose depedencies
final foo = _injector.foo;
final bar = _injector.bar;

class _Injector{
    // create a singleton
    _Injector._internal();
    static final _singleton = new _Injector._internal();
    factory _Injector() {
        return _singleton;
    }

    // the dependecies
    Foo get foo => new Foo();
    Bar get bar => new Bar();
}

This is how the code work, first we create a singleton class _Injector that creates needed dependencies and then exposes these dependencies with top-level variables. This way the dependencies are accessible anywhere the injector.dart package is accessible.

What do you think guys? is this good or is there a better implementation? Thanks

Upvotes: 2

Jonah Williams
Jonah Williams

Reputation: 21441

To implement your own dependency injection I usually use a combination of

  • A 'Bindings' class which has getters to all injected services
  • A static getter/setter which holds a single instances of the Bindings class. This is important for overriding the bindings.

The getters which return classes should lazily construct them if they have dependencies. This allows you to override any parts of your graph by extending the Bindings class and setting it in the global bindings. For example, below I have three classes with the third depending on the first two.

class Foo {}

class Bar {}

class Fizz {
  Fizz(this.foo, this.bar);

  final Foo foo;
  final Bar bar;
}


class Bindings {
  /// Can be final since there are no dependencies
  final Foo foo = new Foo();
  final Bar bar = new Bar();

  Fizz _fizz;
  Fizz get fizz {
    _fizz ??= new Fizz(foo, bar);
    return _fizz;
  }
}

Bindings get bindings => _bindings;
Bindings _bindings;
set bindings(Bindings value) {
  _bindings = value;
}

Now suppose I want to override Foo for testing. I can extend the Bindings class and override the field/getter that returns Foo. and in my test setup, I set bindings with this new instance. Now when Fizz is created, the MockFoo instance is used instead of Foo

class MockFoo implements Foo {}

class BindingsOverride extends Bindings {
  @override
  final Foo foo = new MockFoo();
}

void main() {
  bindings = new BindingsOverride();
}

Edit: In an earlier version I was using a static class. I don't think you need to refer to foo and bar through the bindings instance, you can just refer to the members directly.

Upvotes: 4

Related Questions