vivek
vivek

Reputation: 2867

How to configure providers with custom parameters?

My class depends on some services which needs to take few parameters and then make network call, currently I am passing those parameters and then creating those services via a factory injected into my class. I need to inject those services as a dependency instead, I know that I can create providers for them but in most of the examples I see that the providers are often bound to the fixed values like serveraddres etc. but I need to give then values during run time.

Below is my example code:

public SomeClass {
   private final SomeFactory someFactory;

   @Inject
   SomeClass(SomeFactory factory) {
      someFactory = factory;
   }

   public Foo getFoo(String fooId) {
      FooService fooService = someFactory.getFooService(fooId);
      return fooService.getFoo();
   }

}

What I need to do is:

public SomeClass {
   private final FooService fooService;

   @Inject
   SomeClass(FooService fooService) {
      this.fooService = fooService;
   }

   public Foo getFoo(String fooId) {
      return fooService.getFoo();
   }

}

Update 1

Making the use case more clear:

  @Provides
  @RequestScoped
  public SomeService provideSomeService(Dep1 dep1, String code) throws IOException {
    return new SomeService.Builder()
        .withApplicationName("Foo")
        .setCode(code)  
        .build();
  }

Here, code can be null by default and when needed I can give some value in it.

Can I somehow pass arguments to the provider before its created?

Upvotes: 1

Views: 628

Answers (2)

Cardano
Cardano

Reputation: 951

The best approach here is to parameterize the module and pass the parameter through to a provider that you create at runtime:

public class MyModule extends AbstractModule {
  private final String code;

  public MyModule(String code) {
    this.code = code;
  }

  @Override public void configure() {
    Provider<Dep1> depProvider = getProvider(Dep1.class);
    bind(SomeService.class)
        .toProvider(() -> new SomeService.Builder()
            .withApplicationName("Foo")
            .withDep(depProvider.get())
            .setCode(code)  
            .build())
        .in(RequestScoped.class);
  }
}

Upvotes: 0

Jeff Bowman
Jeff Bowman

Reputation: 95654

If you have a binding for your value (here, code is a String without a binding annotation), then your Update 1 is exactly what the code would look like.

In practice, there are a few differences:

  • Constants like int and String values are generally annotated with a binding annotation, either @Named or a custom annotation.
  • If you need to inject a value into an object graph after Guice initialization, but have a deep enough object graph that dependency injection is still a good idea, you can create a child injector. This way you can make a @Named("code") String accessible within one action or object, but not across your entire Guice application.
  • If your value for code is dynamic enough that it can't be provided through Guice as a key of its own, then you'll have to pass it in using a factory of some sort. For a Builder-based object, I'd say that your SomeFactory implementation is the best that I would come up with in your case.

If you don't need to use a Builder, and can let Guice create the object based on your fields or constructor parameters, you can code-generate a Factory.

  • Guice can generate a factory for you through FactoryModuleBuilder, in a feature known as "assisted injection".
  • Google's other tool, AutoFactory, will code-generate a factory implementation that works in both Guice and Dagger. (It's bundled as "Auto", which includes a model object generator called AutoValue that also generates annotation implementations.)

I put a small demonstration of a child injector and assisted injection in my other SO answer here.

Upvotes: 1

Related Questions