1

I follow this guide for making sub components for my Android application. Here I defined one sub component named LoginComponent used for LoginActivity:

@Subcomponent(modules = LoginModule.class)
public interface LoginComponent {

    void inject(LoginActivity activity);

    @Subcomponent.Builder
    interface Builder {
        Builder requestModule(LoginModule module);
        LoginComponent build();
    }
}


@Module
public class LoginModule {
    @Provides
    LoginManager provideLoginManager(LoginManagerImpl manager) {
        return manager;
    }

    @Provides
    LoginView provideLoginView(LoginViewImpl view) {
        return view;
    }

    @Provides
    LoginPresenter loginPresenter(LoginView view, LoginManager manager) {
        return new LoginPresenterImpl(view, manager);
    }
}

I define this sub component inside another component:

@Module(subcomponents = LoginComponent.class)
public interface AppModule {
}

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

Here is my LoginActivity:

public class LoginActivity extends AppCompatActivity  {

    @Inject LoginPresenter presenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
    }
}

My question is:

  1. purpose of @Subcomponent.Builder. I don't understand this point, because all modules in this component has been defined on @Subcomponent annotation. Why should we duplicate again.
  2. how can I injected LoginPresenter on above activity.

Thanks

Trần Kim Dự
  • 5,872
  • 12
  • 55
  • 107

1 Answers1

4
  1. @Subcomponent.Builder follows the same rules documented in @Component.Builder: You need to have a setter for every Module that Dagger can't initialize itself. (Modules are allowed to have constructor parameters or static factory methods, which would prevent Dagger from creating its own instance.)

    Because Dagger can create a LoginModule by calling a zero-arg constructor, you can drop that method from the Builder; you don't need to recreate it. You might also consider making LoginModule's methods static, which would allow you to make LoginModule an interface or abstract class—then Dagger would be able to avoid holding a reference to the Module at all. Finally, for the simple @Provides methods that bind two classes (e.g. @Provides B provideB(A a) { return a; }) you can switch to @Binds, which would allow the generated code to be even faster.

    At that point, your @Subcomponent.Builder becomes a subcomponent-specific Provider: You can inject your Builder or a Provider<Builder> and get a new subcomponent instance by calling build() on it. As long as you don't need to provide any Module instances, you don't need to have any other methods on your builder.

    You may be able to simply inject a Provider<LoginComponent>, but I haven't seen that in practice, and haven't tried it myself.

  2. To inject a LoginPresenter, you need to get to your AppComponent, create a new LoginComponent, and use it to inject your Activity by passing it in. However, this may be difficult, because you haven't given yourself any access to inject your LoginComponent.Builder.

    Unless you want to try with dagger.android instead, you can add a method to call on your AppComponent to create a new LoginComponent.

    // This way...
    LoginComponent createLoginComponent();
    
    // or this way:
    LoginComponent.Builder createLoginComponentBuilder();
    

    Sometimes you'll see the first way named plus(), which is a naming convention established in Dagger 1; that first way also lets you avoid creating a @Subcomponent.Builder or adding the subcomponent in your AppModule, though you might want to leave the structure the way you have it so you can add more to your AppModule later.

    To finish it off, in Activity.onCreate, you can have your Activity get your Application, access its AppComponent, and inject itself.

    ((YourApplication) getContext().getApplicationContext())
        .getApp()
        .createLoginComponent()
        .inject(this);
    
Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251
  • Is there any point in having a `Subcomponent.Builder` if that `subcomponent` only uses `modules` (and no `@BindsInstance` for example). You could pass the `module` to the `Subcomponent` factory method directly, right? – Florian Walther Aug 02 '19 at 21:10
  • @FlorianWalther Yes, but in order to get to that factory method you'd need a reference to your parent component or subcomponent, which may be heavyweight and difficult to mock or replace, or may create a circular dependency in multi-library projects. Also, if you use a factory on your component instead of an injected builder, Dagger must generate its code even if you never actually call your subcomponent. For those reasons I think it's a best practice to always use a Builder (or new [Component.Factory](https://github.com/google/dagger/releases/tag/dagger-2.22)) even if you have other options. – Jeff Bowman Aug 02 '19 at 21:31
  • But what you describe in your comment assumes that the Builder is not requested over a method on the parent and instead injected directly, right? But in your answer, both ways are using a method on the parent component. So the reference has to be there in any case. – Florian Walther Aug 09 '19 at 09:41
  • @JeffBowman could you help me on this? https://stackoverflow.com/questions/67094274/using-dagger-with-espresso – StuartDTO Apr 16 '21 at 07:10