Milad-Akarie / injectable

Code Generator for get_it
MIT License
550 stars 143 forks source link

Is there a way to inject a factory for a type #116

Closed dalewking closed 1 year ago

dalewking commented 4 years ago

So the injection is all about injection of an instance of a class. What I am looking for is an easy way to inject a function that creates the instance without referencing the getIt instance itself.

Say I have some class Foo:

@injectable
class Foo {/*... */}

And I have another type Bar that has its constructor parameter a function that returns Foo

class Bar {
  final Foo Function() fooBuilder;

  Bar(this.fooBuilder);

  // ..
}

Trying to figure out how to make Bar injectable. I can tell you that just adding @injectable to Bar gives you this error:

NoSuchMethodError: The getter 'name' was called on null. Receiver: null Tried calling: name

So one thought was doing this in a module:

  @injectable
  Foo Function() fooBuilder() => () => GetIt.I();

But that doesn't work:

NoSuchMethodError: The getter 'typeParameters' was called on null. Receiver: null Tried calling: typeParameters

But even if it did work, it is cumbersome if you want to do it a lot and it destroys encapsulation as it requires me to reference the GetIt instance directly in the module.

One way to do it is if I move all of the specification to a module:

  @injectable
  Foo foo() => Foo();

  @injectable
  Bar bar() => Bar(foo);

But if Foo had dependencies I would be forced to deal with that boilerplate in the module instead of letting Injectable deal with it.

If you look at the documentation for Dagger here you can see that they handle this with Lazy and Provider types. They use a specific type for it since functions aren't first class citizens in Java, but you could do it just by looking for function returning an injectable type.

So it would be nice if you could recognize that a dependency is a Function with no parameters returning an Injectable type and create the function automatically.

dalewking commented 4 years ago

This is really becoming a problem for me in my project right now

Milad-Akarie commented 4 years ago

@dalewking my apologies but, my father is a having some health problems these days and I'm no shape to code. I guess you could use a module for the time being

//Injectable will handle foo's deps if it's an
//abstract getter/method
@injectable
Foo foo();

@injectable
Bar bar() => Bar(foo);
dalewking commented 4 years ago

No rush you do what you need to do. If I have some time I will look into it.

I have another work around. I created a type called Supplier:

abstract class Supplier<T> {
  T call<T>();
}

In the same area of code that I call $initGetIt I have this:

@Injectable(as: Supplier)
class GetItSupplier implements Supplier {
  @override
  T call<T>() => GetIt.I();
}

So then Bar is declared like this:

class Bar {
  final Supplier<Foo> fooBuilder;

  Bar(this.fooBuilder);

  Foo get foo => fooBuilder();
}

This works, but I get no build time type checking. If I did not have a binding for Foo in GetIt this would fail at run-time so less than ideal but I do not need to do all the boilerplate.

dalewking commented 4 years ago

OK, ignore what I said about my Supplier workaround as that did not work.

I do now see how to detect if a type is a function. At this point in the code type is FunctionType will tell you that it is a function type. The exception on name is coming from the call to type.element.name in the next line because element is null for FunctionType.

It will take more time to unravel the code to see how to use that.

pal commented 3 years ago

Did you solve this @dalewking? Have a similar situation here

dalewking commented 3 years ago

Think I just changed the design and dealt with the lack of encapsulation by referencing the GetIt instance. It was a proof of concept project that I haven't touched in 2 months, so don't really remember the details any more.

vladimirfx commented 2 years ago

Needs this too. Use case:

We have some async deps in graph but want the whole graph to be sync. It is crucial for our architecture - some sort of 'controlled async' so we can show nice animations etc when, for example, our database is decrypted or initialized. So we incapsulate some async deps in few places in sync graph. For example:


@module
abstract class ProfileRepoModule {
  @singleton
  ProfileRepository provideProfileRepo(
    Future<ProfileDAO> dao,
    RestClient restClient,
  ) => ProfileRepositoryImpl(dao, restClient);
}

This way we await DAO initialization in ProfileRepositoryImpl and not turn whole graph into async. Currently we use ugly workaround for this:


@module
abstract class ProfileRepoModule {
  @singleton
  ProfileRepository provideProfileRepo(
    ServiceLocator sl,
    RestClient restClient,
  ) => ProfileRepositoryImpl(sl.getAsync<ProfileDAO>(), restClient);
}

Where is ServiceLocator our own wrapper around GetIt.

It feels very natural to inject sync or async factories as dependencies. For async it can be implemented very straightforward without any new interface - just for any binging of T support injection of Future<T>. For sync case I do not what is optimal factory interface.

I think that I can provide a PR for async case at least. @Milad-Akarie do you interested in such functionality/PR?

github-actions[bot] commented 1 year ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions