4

I'm implimenting Dagger 2 in my Android app. I have it setup in the following way:

AppComponent.java

@Singleton
@Component(modules = {
  AndroidInjectionModule.class,
  AndroidSupportInjectionModule.class,
  ActivityBuilder.class,
  AppModule.class,
  DataBaseDaoModule.class
})

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

    AppComponent build();
  }

  Application application();
  void inject(MyApplication aApplication);
}

AppInjector.java

ublic class AppInjector {

  public static void init(MyApplication aApplication) {

    //Initialize dagger and inject the aApplication
    DaggerAppComponent.builder().application(aApplication).build().inject(aApplication);

    aApplication.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
      @Override
      public void onActivityCreated(Activity aActivity, Bundle aBundle) {
        handleActivity(aActivity);
      }

      @Override
      public void onActivityStarted(Activity aActivity) {
      }

      @Override
      public void onActivityResumed(Activity aActivity) {
      }

      @Override
      public void onActivityPaused(Activity aActivity) {
      }

      @Override
      public void onActivityStopped(Activity aActivity) {
      }

      @Override
      public void onActivitySaveInstanceState(Activity aActivity, Bundle aBundle) {
      }

      @Override
      public void onActivityDestroyed(Activity aActivity) {
      }
    });
  }

  private static void handleActivity(Activity aActivity) {
    if (aActivity instanceof HasActivityInjector) {
      AndroidInjection.inject(aActivity);
      Timber.d("injected Activity");
    }
    if (aActivity instanceof FragmentActivity) {
      ((FragmentActivity) aActivity).getSupportFragmentManager()
        .registerFragmentLifecycleCallbacks(
          new FragmentManager.FragmentLifecycleCallbacks() {
            @Override
            public void onFragmentCreated(FragmentManager fm, Fragment f,
                                          Bundle savedInstanceState) {
              if (f instanceof Injectable) {
                Timber.d("injected Fragment");
                AndroidSupportInjection.inject(f);
              }
            }
          }, true);
    }
  }
}

AppModule.java

Module(includes = ViewModelModule.class)
class AppModule {

  @Singleton
  @Provides
  ApiService providesApiService(OkHttpClient aClient, MyInterceptor aInterceptor) {

    //Build a Retrofit object here
  }

  @Singleton
  @Provides
  OkHttpClient providesOkHTTPClient(MyInterceptor aInterceptor) {
   //Setup OKHTTP here
  }
}

And finally in MyApplication.Java in the onCreate method I just call the AppInjector like so: AppInjector.init(this);

All of this works and anything I put in my AppComponent's moduels, I can inject into Activities, Fragments and ViewModels.

However, I have cases where I would need a utility class, that depends on Application, for contex - and I use the utility class in various places. Or I will have a Manager class, that depends on Application, or needs something from AppModule. However, since I use these classes outside of Activities, Fragments and ViewModels I cannot just inject. How would I provide my utility classes with their dependencies and any other type of class - like a manager class?

My first thought was to create a UtilityComponent and a ManagerCompoent of sorts, however I have no idea how I would get them to work with anything in AppModuel or through my AppComponent.

Robert J. Clegg
  • 7,231
  • 9
  • 47
  • 99
  • See [this](https://stackoverflow.com/a/45195327/1083957) answer. – azizbekian Sep 19 '17 at 10:29
  • Possible duplicate of [How do I use AndroidInjection class in custom views or other android classes?](https://stackoverflow.com/questions/45075204/how-do-i-use-androidinjection-class-in-custom-views-or-other-android-classes) – azizbekian Sep 19 '17 at 10:30
  • Thanks for that, I however don't plan on using the AndroidInjection() also, I dont need to inject Views. Just Utility classes and possibly other clases, that would never be in an Activity, fragment, or ViewModel really. Will try apply the answer to my case though. – Robert J. Clegg Sep 19 '17 at 10:37
  • 1
    If those classes are constrained to the topmost component (`@Singleton`), than you can acquire those dependencies from your custom `Application` class, which has initialized dependency graph within `onCreate()` method: `MyCustomApplication.getComponent().inject(MyUtilityClass.this)`. This assumes, there is `void inject(MyUtilityClass clazz)` declared in your topmost component. – azizbekian Sep 19 '17 at 10:48
  • Thanks, however what if I needed to use injection in multiple utility classes? Can I do 'void inject(Object clazz)' instead? – Robert J. Clegg Sep 19 '17 at 10:54
  • 1
    Judging by source of generated classes - you cannot do that. You have declare `void inject(...)` methods to your needs. – azizbekian Sep 19 '17 at 11:02
  • Thanks, this works. I feel that I should rather create another component, like, UtilityComponent and UtilityModule, that would then have Each utility class inject into the component. Rather than putting everything into the top most component. – Robert J. Clegg Sep 19 '17 at 11:15
  • The drawback of that approach would be that you'd have a component, that won't be tied up with your main component, thus, for example, if you'd like to have a singleton object `Foo`, you'll **not**, because it will be provided from both `UtilityComponent` and `MainComponent`: they are unaware of each other. – azizbekian Sep 19 '17 at 11:22
  • Thanks. So the suggested way is to use the AppComponent rather then and not another component? – Robert J. Clegg Sep 19 '17 at 11:40
  • You can have the base component, which has a `@Singleton` scoping (your utils dependencies most possibly need to be provided from here). Other components would be declared as subcomponents of the base one. – azizbekian Sep 19 '17 at 11:49

1 Answers1

9

Please don't just use component.inject(myObject) for everything. Always prefer constructor injection or provide it from a module where you can do additional setup steps. .inject(myObject) is intended for Framework components where you don't have access to the constructor.

My first thought was to create a UtilityComponent and a ManagerCompoent of sorts, however I have no idea how I would get them to work with anything in AppModuel or through my AppComponent.

You don't need a separate component for that. See below.

However, since I use these classes outside of Activities, Fragments and ViewModels I cannot just inject.

That has nothing to do with injection. You're talking about scopes, and it sound like your utilities are a @Singleton. Your AppComponent is a @Singleton scoped component, hence it can be used to provide your utils, too.

However, I have cases where I would need a utility class, that depends on Application, for context

If they are part of the @Singleton component, which has access to your Application, they can also be provided anywhere else. No need for more components or anything. Just declare your dependencies and don't overthink it.


Just declare your util, annotate it with @Singleton and mark the constructor with @Inject for constructor injection. @Singleton ensures that it will be provided by your AppComponent and can access the Application on which it depends.

@Singleton public class MyUtil {

  private Application application;

  @Inject public MyUtil(Application application) {
    this.application = application;
  }

}

And then you can just inject it in your Activities, Fragments, or even into other Utilities....

@Singleton public class MyUtilWrapper {

  private MyUtil myUtil;

  @Inject public MyUtilWrapper(MyUtil myUtil) {
    this.myUtil = myUtil;
  }

}

And you can inject either or both into your activity or fragment...

@Inject MyUtil myUtil;
@Inject MyUtilWrapper myUtilWrapper;

void onCreate(..) {
  AndroidInjection.inject(this);
}

You do not need any modules, provides methods, or components to provide simple classes. Just make sure to add the right scope!

David Medenjak
  • 33,993
  • 14
  • 106
  • 134
  • I'm not sure how I missed this! Mind == Blown! Much prefer this way. Having tested it out, it seems, for simple enough classes, I could use this pattern, and for where I need 3rd party classes setup, like Retrofit or some thing that needs more than just a dependancy to get going, I would use a provides method, correct? One quick question - if I did not want my "simple" classes to be a singleton, I would just create a new scope, and component, etc for those? Or add them to AppModule ? – Robert J. Clegg Sep 19 '17 at 17:00
  • Provides methods for additional setup, yes. And yes, just create your own scopes when and where needed. – David Medenjak Sep 19 '17 at 17:53
  • Thanks, this has helped me a great deal! – Robert J. Clegg Sep 19 '17 at 18:14