Fred
Fred

Reputation: 17085

How to get MainActivity inside a module using AndroidInjector

With dagger-android one now can simply write the following and successfully inject the app's dependencies:

@Module
public abstract class MainActivityModule {
  @ContributesAndroidInjector
  abstract MainActivity contributesMainActivity();
}

@Singleton
@Component(modules = {
    AndroidSupportInjectionModule.class,
    AndroidInjectionModule.class,
    AppModule.class,
    MainActivityModule.class
})
public interface ApplicationComponent {
  void inject(BaseApplication baseApplication);

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

    ApplicationComponent build();
  }
}

@Module
public abstract class AppModule {}

public class MainActivity extends AppCompatActivity implements 
  HasSupportFragmentInjector {
  @Inject
  DispatchingAndroidInjector<Fragment> dispatchingAndroidInjector;
  @Inject
  Whatever whatever;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    AndroidInjection.inject(this);
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
  }

  @Override
  public AndroidInjector<Fragment> supportFragmentInjector() {
    return dispatchingAndroidInjector;
  }
}


public class BaseApplication extends Application implements 
  HasActivityInjector {
  @Inject
  DispatchingAndroidInjector<Activity> dispatchingAndroidInjector;

  @Override
  public AndroidInjector<Activity> activityInjector() {
    return dispatchingAndroidInjector;
  }

  @Override
  public void onCreate() {
    DaggerApplicationComponent.builder()
            .application(this)
            .build()
            .inject(this);
    super.onCreate();
  }
}

public class Whatever {
   private final FragmentManager fragmentManager;

   @Inject
   Whatever(MainActivity mainActivity) {
    this.fragmentManager = mainActivity.getSupportFragmentManager();
   }
}

A very trivial example. Basically wire up everything to be able to get the object Whatever with the correct fragment manager injected. This is ok and works.

However, what if I want to use the main activity inside a module? Say I want to actually make Whatever explicitly expose the dependency to the fragment manager by changing the constructor argument:

@Inject
Whatever(FragmentManager fragmentManager) {...}

I now need to provide this dependency. How does one go about it? So I've tried the following:

@Module
public abstract class MainActivityModule {
  @ContributesAndroidInjector
  abstract MainActivity contributesMainActivity();

  @Provides
  static FragmentManager providesFragmentManager(MainActivity activity) {
     return activity.getSupportFragmentManager();
  }
}

Now dagger complains that it cannot find a @Providers for the MainActivity. I thought the @ContributesAndroidInjector method would be able to provide the activity. I'm also a bit puzzled as to how it has no problem injecting the dependency in the first case, but now it cannot do it.

I've also tried to build a factory for this, something like:

public FragmentManagerFactory {
   private final FragmentManager fm;

   @Inject
   FragmentManagerFactory(MainActivity mainActivity){
     this.fm = mainActivity.getSupportFragmentManager();
   }

   public FragmentManager get() {
     return fm;
   }
}

@Module
public abstract class MainActivityModule {
  @ContributesAndroidInjector
  abstract MainActivity contributesMainActivity();

  @Provides
  static FragmentManager providesFragmentManager(FragmentManagerFactory fragmentManagerFactory) {
     return fragmentManagerFactory.get();
  }
}

Which ends up in the same error.

Has anyone managed to do this?? Before it was pretty easy, one would just build the module with the instance of the MainActivity store it in a field and provide that. Now... everything happens behind the curtains.

Would appreciate a lot some help!

Upvotes: 3

Views: 1168

Answers (1)

Blackbelt
Blackbelt

Reputation: 157447

In my setup I have a di/ package in the root of sub-di-packages in my ui/ package. In the ui/di package I have declared the following module and component:

@dagger.Module
public class MainActivityModule {
   @Provides
   FragmentManager provideFragmentManager(MainActivity mainActivity) {
    return mainActivity.getSupportFragmentManager();
   }
}

MainActivitySubcomponent

@Subcomponent(modules = MainActivityModule.class)
public interface MainActivitySubcomponent  extends AndroidInjector<MainActivity> {

    @Subcomponent.Builder
    abstract class Builder extends AndroidInjector.Builder<MainActivity> {
    }
}

on the top-level di package where I keep my ApplicationComponent, I have an additional module

@Module(subcomponents = {MainActivitySubcomponent.class })
public abstract class BuildersModule {

   @Binds
   @IntoMap
   @ActivityKey(MainActivity.class)
   abstract AndroidInjector.Factory<? extends Activity> mainActivityBinder(MainActivitySubcomponent.Builder builder);

}

And as you can imagine BuildersModule, is part of @Component(modules = { of ApplicationComponent.


Disclaimer: I am fairly new to dagger2 and I am still try to figure out a lot of stuff. There may be better ways to achieve what you need.

Upvotes: 7

Related Questions