How do you re-inject an android object, (Service,

2019-08-22 14:29发布

问题:

I have a Service that has a DaggerServiceComponent that will inject all of the Services dependencies successfully.

The problem is that I also have a ServiceManager class, which needs a reference to the Service in order to "Manage" service tasks. i.e.

Application

  public class ApplicationBase extends Application implements HasServiceInjector {

    @Inject
    DispatchingAndroidInjector<Service> dispatchingAndroidServiceInjector;

    protected void setupServiceComponent(Context context) {
        ServiceContextModule serviceContextModule = new ServiceContextModule(context);
        ServiceComponent serviceComponent = DaggerServiceComponent.builder().serviceContextModule(serviceContextModule).build();
        serviceComponent.inject(this);

    }


    @Override
    public AndroidInjector<Service> serviceInjector() {
        return dispatchingAndroidServiceInjector;
    }

}

Component

@Singleton
@Component(modules = {  /* modules */})
public interface ServiceComponent extends AndroidInjector<ApplicationBase > {

    ServiceManager provideServiceManager();
    void inject(ApplicationBase appBase);
    // ..
}

Module

@Module
public abstract class MediaPlaybackServiceModule {

    @ContributesAndroidInjector
    abstract MediaPlaybackService provideMediaPlaybackService();

}

MediaPlaybackService MyService

public class MediaPlaybackService extends MediaBrowserServiceCompat {
   // ...
  private ServiceManager serviceManager;

  @Override
  public void onCreate() {
    init();
    super.onCreate();
    // ...
  }
  /**
   * TO BE CALLED BEFORE SUPER CLASS
   */
  private void init() {
    AndroidInjection.inject(this);
    serviceManager.setMediaPlaybackService(this);
  }

  //...
  // declaration of other dependencies
}

If you look at what I currently do at the moment, I manually add a call to the setter method to set "service" reference in the ServiceManager class which was successfully injected.

The service manager class looks as follows:

public class ServiceManager {

    @Inject
    public ServiceManager(MediaSessionAdapter mediaSession,
                          MyNotificationManager myNotificationManager) {
        this.mediaSession = mediaSession;
        this.notificationManager = myNotificationManager;
    }

    // BODY OF CLASS

    public void setMediaPlaybackService(MediaPlaybackService mediaPlaybackService) {
        this.service = mediaPlaybackService;
    }
}

but I would ideally like to have the service manager constructor to look like

@Inject
public ServiceManager(MediaPlaybackService mediaPlaybackService, MediaSessionAdapter mediaSession,
                      MyNotificationManager myNotificationManager) {
    this.service = mediaPlaybackService;
    this.mediaSession = mediaSession;
    this.notificationManager = myNotificationManager;
}

What changes do I need to make do my dagger2 code to be able to achieve this?

NOTE: for simplicity I have removed the extra code used in the application, but it can be found here

回答1:

So for all those who are facing a similar problem to myself I've been do a lot of experimentation with dagger-android and found it a lot simpler to do the following:

1) Make Activity abstract

public abstract MainActivity extends AppCompatActivity {
  ...
  abstract void initDependencies();
  ...

  @Inject
  public void setMyDependency(MyDependency myDependency) { ... }
}

2) Make a subclass to initialise your dependencies hence removing a dagger dependency in your Activity

public MainActivityProduction extends MainActivity  {
  @Override
  void initDependencies() {
     DaggerMainActivityComponent.factory().create(..., this)
     .inject(this);
  }
  @Override
  public void onCreate() {
    initDependencies();
    super.onCreate();
  }
}

Note this allows for a separate component that could be used for testing as recommended in the dagger documentation https://dagger.dev/testing i.e.

public MainActivityTesting extends MainActivity  {
  @Override
  void initDependencies() {
     DaggerMainActivityTestingComponent.factory().create(..., this)
     .inject(this);
  }
  @Override
  public void onCreate() {
    initDependencies();
    super.onCreate();
  }
}

3) Your component would therefore look like:

@Component(modules = { /* modules */})
public interface MainActivityComponent {
  ...
  @Component.Factory
  interface Factory {
    MainActivityComponent create(...,
                    @BindsInstance MainActivity);
  }
}

meaning that you can have any module refer to your MainActivity

@Module
public class MyDependencyModule {

  @Provides
  public MyDependency providesMyDependency(MainActivity MainActivity) {
     return new MyDependency(mainActivity);
  }
}

This solution:

  1. Decouples the boiler plate setup code from the main code
  2. Allows for test implementations to be easily injected
  3. Removes dependency on the Application class for setting up your dependencies.