Search code examples
javadependency-injectiondaggerassisted-inject

Looking for an example for Dagger assisted injection


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.


Solution

  • 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;
      }
    }