Looking for an example for Dagger assisted injection

user1071136 picture user1071136 · Apr 2, 2014 · Viewed 8.4k times · Source

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.

Answer

Christian Gruber picture Christian Gruber · Apr 2, 2014

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