11
votes

In this question I talk about Dagger2. Dagger2 consists basically of Components and Modules. Here is an example:

Assume I have a interface:

public interface MyCoolService {
  void run();
}

and a possible implementation:

public class MyCoolServiceImpl {
   @Override
   void run() {}
}

I could link the implementation with the interface using Dagger2 generating:

@Component(modules = {MyModule.class})
@Singleton
public interface Component {    
    MyCoolService getMyCoolService();       
}

and

@Module
public class MyModule {

    @Provides @Singleton
    MyCoolService provideMyCoolService() {
        return new MyCoolServiceImpl();
    }
}

This was a brief intro to Dagger2. Now assume I have the following interface:

public interface MySecondCoolService {
  void doCoolStuff();
}

There is no implementation MySecondCoolServiceImpl of MySecondCoolService in code. Instead, I have an Annotations @JustForCoolStuff to mark fields and methods. I created an Annotation processor which collects all these Annotations and generates MySecondCoolServiceImpl which implements MySecondCoolService.

I the compiler knows the new interface MySecondCoolService before the annotation processor is running. So I could change my Component as:

@Component(modules = {MyModule.class})
@Singleton
public interface Component {    
    MyCoolService getMyCoolService();   
    MySecondCoolService getMySecondCoolService();    
}    

The problem is that I do not have an implementation yet in code and I do not know the name of the implementation of MySecondCoolService which will be generated by a annotation processor. Therefore, I cannot wire the interface with the correct implemantation in MyModule. What I can do is changing my annotation processor such that it generates a new module for me. My annotation processor could generate a module (MyGeneratedModule) like this:

@Module
public class MyGeneratedModule {

    @Provides @Singleton
    MySecondCoolService provide MySecondCoolService() {
        return new MySecondCoolServiceImpl();
    }
}  

Again MyGeneratedModule is generated by an annotation processor. I do not have access to it before running the annotation processor also I do not know the name.

Here is the problem: The annotation processor somehow have to tell Dagger2 that there is a new module which Dagger2 should take into account. Since annotation processors cannot change files it cannot extend the @Component(modules = {MyModule.class}) annotation and change it into something like this: @Component(modules = {MyModule.class, MyGeneratedModule.class})

Is there a way to add MyGeneratedModule programmatically to the dagger2 dependency graph? How can my Annotation Processor tell Dagger2 that there should be a new wiring between an interface and an implementation as I have described above?


Foray: I know that something like that can be done in Google Guice and Google Gin. A project which does that is GWTP. There you have a Presenter:

public class StartPagePresenter extends ... {
    @NameToken("start")
    public interface MyProxy extends ProxyPlace<StartPagePresenter> {
    }
    ...
}

which has a @NameToken annotation to a ProxyPlace interface. In your AbstractPresenterModule you wire the view with the presenter and the proxy:

public class ApplicationModule extends AbstractPresenterModule {

        bindPresenter(StartPagePresenter.class,
                StartPagePresenter.MyView.class, StartPageView.class,
                StartPagePresenter.MyProxy.class);
       ...
}

As so can see no implementation of the MyProxy interface is given. The implementation created by a Generator (similar to annotation processor but for GWT). There Generator generates the implementation of StartPagePresenter.MyProxy and add it to the guide/gin system:

public class StartPagePresenterMyProxyImpl extends com.gwtplatform.mvp.client.proxy.ProxyPlaceImpl<StartPagePresenter> implements buddyis.mobile.client.app.start.StartPagePresenter.MyProxy, com.gwtplatform.mvp.client.DelayedBind {

  private com.gwtplatform.mvp.client.ClientGinjector ginjector;
    @Override
    public void delayedBind(Ginjector baseGinjector) {
      ginjector = (com.gwtplatform.mvp.client.ClientGinjector)baseGinjector;
      bind(ginjector.getPlaceManager(),
          ginjector.getEventBus());
      presenter = new CodeSplitProvider<StartPagePresenter>( ginjector.getbuddyismobileclientappstartStartPagePresenter() );
    ...
    }
  }

2
Why can't you just write @Component(modules = {MyModule.class, MyGeneratedModule.class}) in your code? - Tavian Barnes
@TavianBarnes As I described in the text I do not know the name of MyGeneratedModule which will be created. - user4788711
If you have an annotation processor generating a class whose name you don't know in advance, and no other way to instantiate it, then you have a problem. Fix that first, the rest will follow. Whether you use Dagger or not is actually irrelevant. - Thomas Broyer
Annotation processors are no magic, they generate code that you could have written yourself by hand. So first write what you want by hand, then automate it through an annotation processor. If you're in a GWT project, you can still leverage GWT.create() but that won't change much things if your code is generated by an annotation processor. An annotation processor generates code; when a GWT generator both generates code and tells GWT to use it. You have to rethink your approach, not find workarounds. - Thomas Broyer
@stephan1001, in case you didn't know, guice works by reflection, not code generation. it can do LOTS of things at runtime that dagger can't do. the catch is there is performance hit for using reflection. so dagger is faster when creating the object graph, but it has less options in how it generates it. - toadzky

2 Answers

1
votes

Is there a way to add MyGeneratedModule programmatically to the dagger2 dependency graph?

Yes. Use a constructor argument in your module.

@Module
public class MyModule {
  private final MyCoolService serviceImpl;

  public MyModule(MyCoolService serviceImpl) {
    this.serviceImpl = serviceImpl;
  }

  @Provides @Singleton
  MyCoolService provideMyCoolService() {
    return new MyCoolServiceImpl();
  }
}

Initializing the serviceImpl is done when you build the graph:

DaggerComponent.builder().myModule(new MyModule(serviceImpl)).build();
0
votes

Well, It seems that you will have to resort to reflection for this one...

@Module
public class MyGeneratedModule {

    @Provides @Singleton
    MySecondCoolService provide MySecondCoolService() {
        try {
            return (MySecondCoolService) Class.forName("package.MySecondCoolServiceImpl").newInstance(); 
        } catch (Exception ex) { ... }
    }
}