16

I'm reading this great tutorial that explains how @Component.Builderworks in Dagger 2. The author did a good job and the article is straight forward, but there still are some confusing I need to clarify: the default implementation of Dagger 2 looks something like this:

The component:

@Singleton
@Component(modules = {AppModule.class})
public interface AppComponent {

  void inject(MainActivity mainActivity);
  SharedPreferences getSharedPrefs();
}

The module:

@Module
 public class AppModule {

    Application application;

    public AppModule(Application application) {
       this.application = application;
    }

    @Provides
    Application providesApplication() {
       return application;
    }
    @Provides
    @Singleton
    public SharedPreferences providePreferences() {
        return application.getSharedPreferences(DATA_STORE,
                              Context.MODE_PRIVATE);
    }
}

Component instantiating:

DaggerAppComponent appComponent = DaggerAppComponent.builder()
         .appModule(new AppModule(this)) //this : application 
         .build();

According to the article, we can simplify this code even more by avoiding passing arguments to the module constructor using @Component.Builder and @BindsInstance annotations, then the code will look like this:

The component:

@Singleton
@Component(modules = {AppModule.class})
public interface AppComponent {
   void inject(MainActivity mainActivity);
   SharedPreferences getSharedPrefs();

   @Component.Builder
   interface Builder {
      AppComponent build();
      @BindsInstance Builder application(Application application);      
  }

}

The module:

@Module
 public class AppModule {

     @Provides
     @Singleton
     public SharedPreferences providePreferences(
                                    Application application) {
         return application.getSharedPreferences(
                                    "store", Context.MODE_PRIVATE);
     }
 }

And the component instantiating:

DaggerAppComponent appComponent = DaggerAppComponent.builder()
           .application(this)
           .build();

I almost understand how the above code works, but here is the part I don't understand: how did we get from appModule(new AppModule(this)) to application(this) when we are instantiating the component?

I hope the question was clear and thanks.

Toni Joe
  • 7,715
  • 12
  • 50
  • 69

3 Answers3

21

tl;dr Dagger will create any no-arg constructor models itself if you don't pass them in and usage of @BindsInstance might be better optimized than providing types from a module.


First you had a component that requires an Application to be constructed. So you construct the module and pass it to the component.

Now, with the component builder, you can just bind single objects to a component. This is an alternative to what we did above. There is no longer the need for a module and we can just directly hand Dagger the object that we want in our component.
As it is with @Binds to provide interface implemenetations, you can often assume that Dagger can and will optimize features like those better than the simple approach using a module, since the intention is more clearly marked.

So using the @BindsInstance will add the type to our component so that we no longer need the module to provide it. We can now also remove the parameter from the module constructor.

how did we get from appModule(new AppModule(this)) to application(this) when we are instantiating the component?

Since Dagger can instantiate no-args modules itself there is no longer the need to explicitly add the module to the component, and we can replace that line with the new .application(this) call.

David Medenjak
  • 33,993
  • 14
  • 106
  • 134
  • If Dagger can instantiate no-args modules itself, we cold just use `DaggerAppComponent.builder().build();`, so what `appliction(this)` is for then? – Toni Joe Sep 13 '17 at 12:50
  • 1
    Check your `Component.Builder` interface! You register a method that binds the application to the component, which you have to call: `@BindsInstance Builder application(Application application);` – David Medenjak Sep 13 '17 at 12:52
  • Now I see! Thank you very much David Medenjak you were very helpful – Toni Joe Sep 13 '17 at 12:55
7
 @Component.Builder
   interface Builder {
      AppComponent build();
      @BindsInstance Builder application(Application application);      
  }

When we call the method

application(Application application)

from Application class

.application(this)

It will set our application object to the AppComponent. So inside the appcomponet the application instance is available.

So we can remove the below code from application module because dagger will automatically inject application instance wherever it required.

Application application;
   public AppModule(Application application) {
       this.application = application;
    }

    @Provides
    Application providesApplication() {
       return application;
    }

Also Dagger instantiate all modules with default constructor.

If you want a Context object from AppModule just write

@Module
public class AppModule {

   @Provides
    Context provideContext(Application application) {
        return application;
    }
}
Nidhin
  • 1,818
  • 22
  • 23
  • whats the point of the last snippet? if a client wants a context from AppModule, why wouldn't they just use the parameter they are passing in – Ryhan Aug 28 '19 at 21:14
  • if you use @Inject Context context; in your activity. It will work only if its module provide the Context object – Nidhin Aug 29 '19 at 05:40
  • Nevermind I misunderstood your original point of the snippet, you were just trying to express how you would inject from AppModule. Do you know if there's a way to get a reference to the application instance without passing through parameter in AppModule – Ryhan Aug 30 '19 at 07:30
  • I think you can use @Binds for that. Answer of keisar for this answer may helpful https://stackoverflow.com/questions/30692501/dagger-2-injecting-android-context. I am not tried this yet. – Nidhin Aug 30 '19 at 09:21
0

:: >> I found Error like this:...

" AppComponent appComponent = DaggerAppComponent.builder().application(this).build(); "

Follow Steps Below

  1. Delete that line "import com.joiibmed.Dagger2.DaggerAppComponent;" from import section.
  2. Run App and it will again giving you same error.
  3. On that time go to that function where import error comes. and You get suggestion to "import class"
  4. Run App again. Solved.

☻♥ Done Keep Code