25

From dagger-discuss@:

I have a class that gets some dependencies from the object graph, and other dependencies from a caller at runtime.

public class ImageDownloader {
  // Get these dependencies from the injector.
  private final HttpClient httpClient;
  private final ExecutorService executorService;

  // Get these from the caller.
  private final URL imageUrl;
  private final ImageCallback callback;

  ...
}

I came up with a solution, where I define a Factory,

public class ImageDownloader {
  ...
  public static class Factory {
    private final HttpClient httpClient;
    private final ExecutorService executorService;

    @Inject
    public Factory(HttpClient httpClient, ExecutorService executorService) {
      this.httpclient = httpClient;
      this.executorService = executorService;
    }

    public ImageDownloader create(URL imageUrl, ImageCallback callback) {
      return new ImageDownloader(httpClient, executorService, iamgeUrl, callback);
    }
  }
  ...
}

Now, instead of injecting ImageDownloader in the client's constructor, I simply inject ImageDownloader.Factory and call its create() method.

As you can see, that's quite verbose and long. It also has a bunch of duplication and boilerplate. There're some obstacles to annotating the fields themselves with @Inject, so let's ignore this possibility for now.

The Square people have come up with an interesting solution, using providers. Define a Factory interface,

public class ImageDownloader {
  ...
  public interface Factory {
    ImageDownloader create(URL imageUrl, ImageCallback callback);
  }
}

and then provide it in a module,

public class ImageModule {
  ...
  @Provides 
  public ImageModule.Factory provideImageModuleFactory(
      final Provider<HttpClient> httpClientProvider, 
      final Provider<ExecutorService> executorServiceProvider) {
    return new ImageDownloader.Factory() {
      public ImageDownloader create(URL imageUrl, ImageCallback callback) {
        return new ImageDownloader(httpClientProvider.get(), executorServiceProvider.get(),
            imageUrl, callback);
      }
  }
  ...
}

(again, from dagger-discuss@).

My ImageDownloader is a class that's injected by a class which is injected by another class which is injected by yet another class, ..., which is referenced in a @Module. This all somehow* works, and all classes are found in build time. Now, to add a module, I have to explicitly let the object graph know about it.

I must be missing something - it's very easy to inject a new class, but very tedious to add a new module.

My question is: how is assisted injection done in practice? anyone has an example? how should I use ImageModule, if at all?

* - "somehow" does indeed imply it's partly magic to me.

user1071136
  • 15,636
  • 4
  • 42
  • 61

4 Answers4

18

So, some of the Dagger/Guice folks at Google created a thing called AutoFactory (http://github.com/google/auto) in a project that includes AutoFactory (code-generated assisted injection), AutoValue (code-generated custom value types) and AutoService (auto-generation of java services metadata files).

AutoFactory pretty much operates like you would expect - it generates the factory you would otherwise have hand-rolled. It's a very early version, and we have a lot more flexibility planned, but it will generate a factory class that will take a type that includes some JSR-330 injectable dependencies and some call-stack parameters, and merge them together in creating instances of the annotated type.

In essence it will generate the factory you wrote, automatically if you properly annotate your factory-created type.

For instance, if you create your class:

@AutoFactory
public class ImageDownloader {
  // Get these dependencies from the injector.
  private final HttpClient httpClient;
  private final ExecutorService executorService;

  // Get these from the caller.
  private final URL imageUrl;
  private final ImageCallback callback;

  ImageDownloader(
      @Provided HttpClient httpClient,
      @Provided ExecutorService executorService,
      ImageCallback callback,
      URL imageUrl) {
    // assignments
  }
}

AutoFactory will generate:

@Generated("com.google.auto.factory.processor.AutoFactoryProcessor")
public final class ImageDownloaderFactory {
  private final Provider<ExampleClasses.HttpClient> httpClientProvider;
  private final Provider<java.util.concurrent.ExecutorService> executorServiceProvider;

  @Inject
  public ImageDownloaderFactory(
      Provider<ExampleClasses.HttpClient> httpClientProvider,
      Provider<java.util.concurrent.ExecutorService> executorServiceProvider) {
    this.httpClientProvider = httpClientProvider;
    this.executorServiceProvider = executorServiceProvider;
  }

  public ImageDownloader create(ImageCallback callback, URL imageUrl) {
    return new ImageDownloader(
        httpClientProvider.get(), 
        executorServiceProvider.get(), 
        callback, 
        imageUrl);
  }
}

(Note, we have a bunch of clean-up to do on the output source, but the above is basically what is generated, though not quite as nicely formatted.)

The resulting class is then, properly a JSR-330 compliant injectable class, which you can inject in your dependency graph (in Dagger or Guice) and it will create these objects for you, co-mingling the call-stack state with the provided dependencies appropriately.

You can inject the above Just-In-Time, or you can provide it via an @Provides method at your leisure.

You can even have the factory implement a factory interface, and then simply bind the two together in a dagger module like so:

@AutoFactory(implementing = MyFactoryInterface.class)
public class ImageDownloader {
  // ... otherwise as above...
}

@Module(...)
class MyModule {
  @Provides MyFactoryInterface factoryImpl(ImageDownloaderFactory impl) {
    return impl;
  }
}
Christian Gruber
  • 4,691
  • 1
  • 28
  • 28
  • I am releasing 0.1-beta1 of AutoFactory as I type this. – Christian Gruber Apr 02 '14 at 03:38
  • Thanks for the pointer! Still, I'm curious how AI is done in practice, and looking for an example. Tangentially, is there a way to use my module without adding it explicitly to other, not-under-my-control modules? – user1071136 Apr 02 '14 at 17:03
  • Sure - I'll expand on the answer a bit. – Christian Gruber Apr 02 '14 at 17:37
  • Thanks for the clarification. However, it doesn't answer the original question, looking for an example of how it's done right now, before AutoFactory. Thanks again! – user1071136 Apr 02 '14 at 18:14
  • As to how it's done in practice is pretty much how AutoFactory does it, just with hand-rolled factories, as far as I have seen. But it is not often (yet) done with Dagger, because no one likes writing boilerplate, and the tool was not there. – Christian Gruber Apr 02 '14 at 20:26
  • I'm confused by this: "It's easy to inject a new class, but very tedious to add a new module." You can simply make your Factory inner type a class that is injectable, rather than an interface and then inject it everywhere, and Dagger will implicitly bind it. That seems very easy, and no new module required. No? – Christian Gruber Apr 02 '14 at 21:59
  • Yes - I was referring to the solution involving a Provides inside a Module. Seems awkward to me to add it to some unrelated root module, just to be able to assist-inject. – user1071136 Apr 02 '14 at 22:16
  • Oh yeah - but you don't need to add it to an unrelated root module, you add it to a module beside your code, and include that module from an app module (or wherever). Module includes are designed to allow you to break down your code that way. But I wouldn't add it to a module, I'd just make the factory a concrete class. – Christian Gruber Apr 03 '14 at 01:18
  • @ChristianGruber What is MyFactoryInterface in your module factoryImpl function? – Michael Mar 21 '15 at 11:45
  • @ChristianGruber Could you please explain how the generated ImageDownloaderFactory can be used and integrated into the dagger object graph? – Michael Mar 21 '15 at 12:04
  • @ChristianGruber Does the AutoFactory work with the Dagger Lazy type? I alway get the error: ``cannot be provided without an @Provides-annotated method.`` – Michael Mar 21 '15 at 17:05
  • Sorry - edited. MyFactoryInterface is a factory interface you declare in @AutoFactory(implementing=...) – Christian Gruber Mar 26 '15 at 19:10
  • The generated ImageDownloaderFactory can simply be injected normally in any JSR-330 compliant inejction system, including dagger, merely by using it at an injection point (e.g. @Inject SomeConstructor(ImageDownloaderFactory factory) { ... } ). Or, with the custom factory interface, injected as that interface, and bound using the module given in the example above. – Christian Gruber Mar 26 '15 at 19:12
  • @confile - I'm not sure what you mean "Works with Lazy". I'd need an example. Can you maybe post a different SO question? – Christian Gruber Mar 26 '15 at 19:12
6

As @xsveda said, for assisted injection you probably want to use AssistedInject. I wrote about it in this blogpost, but I'll add a full example here to make things easier.

First thing you need are the dependencies:

compileOnly 'com.squareup.inject:assisted-inject-annotations-dagger2:0.5.0'
kapt 'com.squareup.inject:assisted-inject-processor-dagger2:0.5.0'

Then here's how your example would look like:

class ImageDownloader @AssistedInject constructor(
  private val httpClient: HttpClient,
  private val executorService: ExecutorService,
  @Assisted private val imageUrl: URL,
  @Assisted private val callback: ImageCallback
) {

  @AssistedInject.Factory
  interface Factory {
    fun create(imageUrl: URL, callback: ImageCallback): ImageDownloader
  }
}

First thing is that instead of annotating the constructor with @Inject, we annotate it with @AssistedInject. Then we annotate the parameters that will have to go through the factory, which is the opposite of what AutoFactory expects. Finally, we need an inner factory interface annotated with @AssistedInject.Factory that has a single method that receives the assisted parameters and returns the instance we're interested in.

Unfortunately, we still have an extra step here:

@AssistedModule
@Module(includes = [AssistedInject_AssistedInjectModule::class])
interface AssistedInjectModule

We don't necessarily need a dedicated module for it, even though that's a valid option. But we can also have those annotations in another module that is already installed in the component. The nice thing here is that we only need to do it once, and after that any factory will automatically become part of the graph.

With that, you can basically inject the factory and ask for your object as you'd normally do.

Fred Porciúncula
  • 8,533
  • 3
  • 40
  • 57
0

You can do assisted injection with Dagger using square/AssistedInject

Please check also my original answer here: https://stackoverflow.com/a/53719342/2862474

xsveda
  • 17,074
  • 2
  • 16
  • 16
0

As of Dagger 2.31 from January 2021, Dagger now natively supports assisted injection, which is recommended over the Square and Auto options. Those other options still work, but may require extra setup compared to the native option.

For your case you'd need to define the factory interface using @AssistedFactory, and then specify in the constructor which arguments come from it:

public class ImageDownloader {
  @AssistedFactory
  public interface Factory {
    ImageDownloader create(URL imageUrl, ImageCallback callback);
  }

  @AssistedInject
  public Factory(
      HttpClient httpClient,
      ExecutorService executorService,
      @Assisted URL imageUrl,
      @Assisted ImageCallback callback) {
    // ...
  }
}

Your Module wouldn't need to be involved, and note the absence of @Inject in comparison to @AssistedInject. As in the docs also linked above, there are options when two arguments of the same type are supplied via the factory.

Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251