Purpose of proxyProvide in Dagger 2 generated code
Asked Answered
G

1

13

I have this Dagger module. I want to understand the generated code so I can verify that my Dagger configuration is optimal.

@Module
public class TypefaceModule {

    @Provides @Singleton @Named("Roboto Light")
    static Typeface provideRobotoLight(AssetManager assets) {
        return Typeface.createFromAsset(assets, "fonts/Roboto-Light.ttf");
    }

}

Here's the generated code (Dagger 2.14.1):

public final class TypefaceModule_ProvideRobotoLightFactory implements Factory<Typeface> {
  private final Provider<AssetManager> assetsProvider;

  public TypefaceModule_ProvideRobotoLightFactory(Provider<AssetManager> assetsProvider) {
    this.assetsProvider = assetsProvider;
  }

  @Override
  public Typeface get() {
    return Preconditions.checkNotNull(
        TypefaceModule.provideRobotoLight(assetsProvider.get()),
        "Cannot return null from a non-@Nullable @Provides method");
  }

  public static TypefaceModule_ProvideRobotoLightFactory create(
      Provider<AssetManager> assetsProvider) {
    return new TypefaceModule_ProvideRobotoLightFactory(assetsProvider);
  }

  public static Typeface proxyProvideRobotoLight(AssetManager assets) {
    return Preconditions.checkNotNull(
        TypefaceModule.provideRobotoLight(assets),
        "Cannot return null from a non-@Nullable @Provides method");
  }
}

There are two functions which do almost the same thing: the instance method get(), and the static method proxyProvideRobotoLight().

Why has Dagger generated two versions of this code, which both call the module's provide() method statically? Can't one call the other?

(Incidentally, I do realise that I no longer need to bundle fonts in my app assets. That's not the question here.)

Grooms answered 22/1, 2018 at 11:51 Comment(3)
Nice observations and interesting question. Seems like we need someone from dagger team to answer the question.Muffin
It is not really transparent here as method was static, so in both cases (.get and .proxyProvide) it is called without using module instance, otherwise get would use references passed and stored via constructor (including module instance) and proxyProvide would expect all required details to be passed as arguments. Suppose there is still room for improvement, i.e. detect and reuse static method in generated code.Gyn
@AivarasPrudnikovas would you care to write that up as an answer, perhaps showing some examples of how they would be called differently?Grooms
H
5

First off: Dagger generates this code ahead-of-time so that in a modularized build, you get better build performance. Because of that, we don't know which (or both, or neither) that you will need, so we generate both just in case, and assume that Proguard will be able to strip whatever is unused.

So what are both actually doing?

The first (the get() method) is invoked when the binding that this factory represents is requested as a Provider<T>. That can happen either directly, or if the binding is scoped, or a few other scenarios.

The second case is what we call inlining. Suppose you have a @Provides method in a module, and you have a method on your @Component that returns that type. The most ideal code to generate is something like:

@Override
public YourBinding y() {
  return YourModule.yourProvidesMethod();
}

The thing is, that provides method may not be accessible from the same package as your component, so we generate this "proxy" method which gives Dagger the right accessibility. It also makes accessible all of the parameters to that method, erasing them to Object if necessary. And if they do get erased (think of this like generic type erasure), we need to then insert the casts to the correct types inside the proxy method.

The Provider.get() implementation doesn't need that, because there, all of the types should are accessible by the code that invokes it.

So to sum up - we want to generate both versions, hopefully you should only use one, and Proguard should clean up the other.

Hope that helps!

Hermes answered 15/2, 2018 at 16:48 Comment(6)
So the only purpose of these provideProxy methods is to get access if the module is in another package?Hols
Also, are Providers not used by Dagger internally if we neither add scope annotations nor request a Provider directly?Hols
They're also used if any of the parameters need to be null checked. In many cases they'll be used.Hermes
Providers are also use if the binding is requested by another type for which a Provider is needed (for the reasons you mentioned). So if you have A -> B -> C, an A is scoped, B and C will have Provider instancesHermes
Thank you for the clarification. In earlier versions of Dagger, everything was returned from Provider<>s, whereas in newer versions most of the calls have been inlined to make the code more efficient. Is that correct? In a very simple scenario without scope I have created, no _Factory is ever instantiated as a Provider<>Hols
Also, do you have a quick explanation for why scope requires a Provider wrapper? Couldn't the object instantiation still be inlined without a Provider?Hols

© 2022 - 2024 — McMap. All rights reserved.