Looking for an example for Dagger assisted injection
Asked Answered
R

4

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.

Redbug answered 2/4, 2014 at 0:28 Comment(0)
D
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;
  }
}
Damiandamiani answered 2/4, 2014 at 3:38 Comment(14)
I am releasing 0.1-beta1 of AutoFactory as I type this.Damiandamiani
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?Redbug
Sure - I'll expand on the answer a bit.Damiandamiani
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!Redbug
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.Damiandamiani
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?Damiandamiani
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.Redbug
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.Damiandamiani
@ChristianGruber What is MyFactoryInterface in your module factoryImpl function?Krick
@ChristianGruber Could you please explain how the generated ImageDownloaderFactory can be used and integrated into the dagger object graph?Krick
@ChristianGruber Does the AutoFactory work with the Dagger Lazy<T> type? I alway get the error: cannot be provided without an @Provides-annotated method.Krick
Sorry - edited. MyFactoryInterface is a factory interface you declare in @AutoFactory(implementing=...)Damiandamiani
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.Damiandamiani
@confile - I'm not sure what you mean "Works with Lazy". I'd need an example. Can you maybe post a different SO question?Damiandamiani
P
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.

Palais answered 9/4, 2019 at 16:36 Comment(0)
S
0

You can do assisted injection with Dagger using square/AssistedInject

Please check also my original answer here: https://mcmap.net/q/539629/-can-i-use-some-kind-of-assisted-inject-with-dagger

Sacramentalist answered 11/12, 2018 at 7:35 Comment(0)
A
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.

Arapaho answered 29/9, 2022 at 18:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.