user4788711
user4788711

Reputation:

Delayed Binding to Dagger2 Graph using Annotation Processing

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() );
    ...
    }
  }

Upvotes: 11

Views: 783

Answers (2)

Karl the Pagan
Karl the Pagan

Reputation: 1965

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();

Upvotes: 1

bennyl
bennyl

Reputation: 2956

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) { ... }
    }
}  

Upvotes: 0

Related Questions