1 introduction

It has been half a year since I first contacted Dagger2. From the initial muddlehead to the gradually proficient use of Dagger2, this process is really thanks to MVPArms. I really learned a lot in MVPArms in the past six months, and the evolution of MVVMArms can be said to be the crystallization of this half year’s learning. In the process of building MVVMArms, the latest Dagger2.11 was adopted to better support the Dependency injection of Android. All right, so much nonsense, let’s take a closer look at the Dagger.Android with an example.

Download the source code together will be better! Download the source code together will be better! Download the source code together will be better!

DaggerAndroid:github.com/xiaobailong…

If you are not familiar with Dagger2, you can read my reprinted article Dagger2 Learning, which explains the concept very clearly. So far, most of the articles have explained how to use Dagger2 simply, but there is no article about how to manage dependencies between multiple modules using Dagger. I will share a Dagger.Android multi-module management scheme explored in MVVMArms.

2 Gradle configuration

To use Dagger2 on Android, add Gradle configuration first. The latest version can be found on GitHub. Android Studio 3.0 Beta6 is used here.

  //dagger.android
  implementation 'com. Google. Dagger: a dagger: 2.11'
  annotationProcessor 'com. Google. Dagger: a dagger - compiler: 2.11'
  implementation 'com. Google. Dagger: a dagger - android: 2.11'
  implementation 'com. Google. Dagger: a dagger - android - support: 2.11'
  annotationProcessor 'com. Google. Dagger: a dagger - android - processor: 2.11'Copy the code

Introduction to 3 piece

Let’s start with a few key concepts:

  • @Component: Component is an injector. Like a syringe, Component initializes the dependency in the target class by injecting instances of the dependency into the target class.
  • @subComponent: As the name suggests, this is also an injector, but annotated by @SubComponent is one level below the annotated by @Component, just as Subcomponent inherits from Component.
  • @Module: An object that Provides injection for some third-party libraries, often in conjunction with @provides.
  • AndroidInjectionModule: Mainly provides the Dagger.Android Component package, which should be included in modules of the Component injector that injects the Application.
  • AndroidInjection: Dagger. Core class for Android injection, which encapsulates static methods used to inject four components and fragments.

Dagger.Android can have two injection modes. Let’s look at Activity and Fragment respectively.

3.1 Activity Dependency Injection (the first injection method)

3.1.1 AndroidInjectionModule

Add AndroidInjectionModule to the entire Application Component. The AndroidInjectionModule mainly provides the Dagger.Android Component package, which should be included in modules that inject the Component injector into the Application. In this case, AppComponent.

AppComponent

  @Singleton
  @Component(modules = AndroidInjectionModule.class)
  public interface AppComponent {
      void inject(MainApp mainApp);
  }Copy the code

This ensures that you have the latest Dagger.Android.

3.1.2 @ Subcomponent

This interface needs to inherit public Interface AndroidInjector; The interface has an abstract class derived from AndroidInjector.Builder annotated by @SubComponent. Builder, where the generic T is the target Activity to inject.

MainActivitySubcomponent

  @ActivityScope
  @Subcomponent(modules = KobeModule.class)//DataModule
  public interface MainActivitySubcomponent extends AndroidInjector<MainActivity> {
      @Subcomponent.Builder
      abstract class Builder extends AndroidInjector.Builder<MainActivity> {}}Copy the code

Some data types that need to be injected can be included in @subComponent (modules = {}).

KobeModule

@Module
  public class KobeModule {
      @ActivityScope
      @Provides
      public Person provideKobe(a) {
          return new Person("Kobe".39); }}Copy the code

Person

  public class Person {
      private String name;
      private int age;

      @Inject
      public Person(String name, int age) {
          this.name = name;
          this.age = age;
      }

      public String getName(a) {
          return name;
      }

      public int getAge(a) {
          returnage; }}Copy the code

3.1.3 @ the Module

Next, write the Activity Module and bind it to the Subcomponent created in the previous step. The MainActivitySubcomponent then injects the content provided in the MainActivityModule into the MainActivity. Then add it to the global Component, the AppComponent, so that the AppComponent is connected to the MainActivitySubcomponent by inheritance. Subcomponent is the next level of Component.

MainActivityModule

  @Module(subcomponents = MainActivitySubcomponent.class)
  public abstract class MainActivityModule {
      /** * The first injection mode. Need a Subcomponent * /
      @Binds
      @IntoMap
      @ActivityKey(MainActivity.class)
      abstract AndroidInjector.Factory<? extends Activity>
      bindActivityInjectorFactory(MainActivitySubcomponent.Builder builder);
  }Copy the code

AppComponent could be rewritten as:

  @Singleton
  @Component(modules = {AndroidInjectionModule.class,
          MainActivityModule.class)
  public interface AppComponent {
      void inject(MainApp mainApp);
  }Copy the code

3.1.4 HasActivityInjector

Let MainApp HasActivityInjector interface is implemented, and inject DispatchingAndroidInjector. AndroidInjector provides the AndroidInjector for the Activity, which is required by AndroidInjection. Inject (Activity Activity). See 3.1.6 source code for details.

MainApp

  public class MainApp extends Application implements HasActivityInjector {
      @Inject
      DispatchingAndroidInjector<Activity> mActivityInjector;

      private AppComponent mAppComponent;

      @Override
      public void onCreate(a) {
          super.onCreate();

          mAppComponent = DaggerAppComponent.builder()
                  .daggerComponent(getDaggerComponent())
                  .build();
          mAppComponent.inject(this);
      }

      public AppComponent getAppComponent(a) {
          return mAppComponent;
      }

      @Override
      public AndroidInjector<Activity> activityInjector(a) {
          returnmActivityInjector; }}Copy the code

3.1.5 AndroidInjection

Finally, the injection is done in the onCreate() method of the target Activity, noting that it should be done before the super.oncreate () call.

MainActivity

  public class MainActivity extends AppCompatActivity {
    @Inject
    Person mKobe;// dependency injection

    public void onCreate(Bundle savedInstanceState) {
      AndroidInjection.inject(this);
      super.onCreate(savedInstanceState); }}Copy the code

Finally, don’t forget to specify MainApp in androidmanifest.xml.

3.1.6 Source code analysis

AndroidInjection. Inject () from a MainApp DispatchingAndroidInjector object, and MainActivity into to inject (Activity Activity) method. DispatchingAndroidInjector for MainActivity class lookup AndroidInjector. Factory implementation class, namely MainActivitySubcomponent. Builder; This creates an AndroidInjector, MainActivitySubcomponent, and passes the MainActivity into the Inject (Activity Activity) method.

AndroidInjection#inject(Activity Activity)

  public static void inject(Activity activity) {
    checkNotNull(activity, "activity");
    Application application = activity.getApplication();
    // Check whether the Application has implemented the HasActivityInjector interface
    if(! (applicationinstanceof HasActivityInjector)) {
      throw new RuntimeException(
          String.format(
              "%s does not implement %s",
              application.getClass().getCanonicalName(),
              HasActivityInjector.class.getCanonicalName()));
    }

    / / from the Application to obtain AndroidInjector object, namely DispatchingAndroidInjector < Activity > mActivityInjector
    AndroidInjector<Activity> activityInjector =
        ((HasActivityInjector) application).activityInjector();
    checkNotNull(
        activityInjector,
        "%s.activityInjector() returned null",
        application.getClass().getCanonicalName());

    / / the last into MainActivity, here is the Dagger compiler generated DaggerAppComponent. MainActivitySubcomponentImpl class.
    activityInjector.inject(activity);
  }Copy the code

3.2 Fragment Dependency Injection (Second Method)

Fragments are using v4 compatible in android. Support. The v4. App. The fragments.

3.2.1 @ Subcomponent

Since the AndroidInjectionModule was added in the first step of the Activity dependency injection, it can be used directly here. This way is the first way to simplify, if MainFragmentSubcomponent and MainFragmentSubcomponent. No other method Builder or supertype, as follows,

MainFragmentSubcomponent

  @FragmentScope
  @Subcomponent
  public interface MainFragmentSubcomponent extends AndroidInjector<MainFragment> {
      @Subcomponent.Builder
      abstract class Builder extends AndroidInjector.Builder<MainFragment> {}}Copy the code

The MainFragmentSubcomponent can be omitted, that is, the MainFragmentSubcomponent can be omitted.

3.2.2 @ the Module

When the Subcomponent and its Builder have no other methods or supertypes, the Subcomponent is no longer needed. Subcomponent role is actually generates AndroidInjector, but @ ContributesAndroidInjector annotations can also do this for us.

MainFragmentModule

  @Module
  public abstract class MainFragmentModule {
      /** * Second injection mode. Subcomponent */ is not needed when Subcomponent and its Builder have no other methods or supertypes
      @FragmentScope
      @ContributesAndroidInjector(modules = JordonModule.class)//DataModule
      abstract MainFragment contributeMainFragment(a);
  }Copy the code

Some can be included in the data type of the need to inject @ ContributesAndroidInjector (modules = {}).

Load the MainFragmentModule into the AppComponent:

  @Singleton
  @Component(modules = {AndroidInjectionModule.class,
          MainActivityModule.class,
          MainFragmentModule.class})
  public interface AppComponent {
      void inject(MainApp mainApp);
  }Copy the code

3.2.3 HasSupportFragmentInjector

Let to dependency injection target fragments (namely MainFragment) host the Activity (i.e., MainActivity) implementation HasSupportFragmentInjector interface. AndroidInjector provides AndroidInjector for fragments, which is required by AndroidInjection. Inject (Fragment Fragment). See 3.2.5 source code for details.

MainActivity

  public class MainActivity extends AppCompatActivity implements HasSupportFragmentInjector {
    @Inject
    DispatchingAndroidInjector<Fragment> mFragmentInjector;
    / /...

    public void onCreate(Bundle savedInstanceState) {
      AndroidInjection.inject(this);
      super.onCreate(savedInstanceState);
    }

    @Override
    public AndroidInjector<Fragment> supportFragmentInjector(a) {
        return this.mFragmentInjector; }}Copy the code

If you use the android. App. Fragments, the Activity should be implemented HasFragmentInjector interface, and inject DispatchingAndroidInjector. This step can also implement HasSupportFragmentInjector in Application interface, similar 3.1.4 described below.

3.2.4 AndroidInjection

Finally, inject in the onAttach() method of the target Fragment.

MainFragment

  public class MainFragment extends Fragment {
    @Inject
    Person mJordon;// dependency injection

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        AndroidSupportInjection.inject(this);
    }

    / /...
  }Copy the code

3.2.5 Source code analysis

Principle and the Activity of fragments into similar, here again, in fact, the second way is the first way to simplify, using the @ ContributesAndroidInjector annotations to automatically generate a Subcomponent.

AndroidSupportInjection# Inject (Fragment Fragment)

  public static void inject(Fragment fragment) {
    checkNotNull(fragment, "fragment");
    / / get HasSupportFragmentInjector
    HasSupportFragmentInjector hasSupportFragmentInjector = findHasFragmentInjector(fragment);
    Log.d(
        TAG,
        String.format(
            "An injector for %s was found in %s",
            fragment.getClass().getCanonicalName(),
            hasSupportFragmentInjector.getClass().getCanonicalName()));
    / / derives from the Activity HasSupportFragmentInjector object, namely DispatchingAndroidInjector < fragments > mFragmentInjector
    AndroidInjector<Fragment> fragmentInjector =
        hasSupportFragmentInjector.supportFragmentInjector();
    checkNotNull(
        fragmentInjector,
        "%s.supportFragmentInjector() returned null",
        hasSupportFragmentInjector.getClass().getCanonicalName());
    / / the last into MainFragment, here is the Dagger compiler generated DaggerAppComponent. MainFragmentSubcomponentImpl class.
    fragmentInjector.inject(fragment);
  }Copy the code

AndroidSupportInjection#findHasFragmentInjector(Fragment fragment)

  private static HasSupportFragmentInjector findHasFragmentInjector(Fragment fragment) {
      Fragment parentFragment = fragment;
      while((parentFragment = parentFragment.getParentFragment()) ! =null) {
        if (parentFragment instanceof HasSupportFragmentInjector) {
          return (HasSupportFragmentInjector) parentFragment;
        }
      }
      Activity activity = fragment.getActivity();
      if (activity instanceof HasSupportFragmentInjector) {
        return (HasSupportFragmentInjector) activity;
      }
      if (activity.getApplication() instanceof HasSupportFragmentInjector) {
        return (HasSupportFragmentInjector) activity.getApplication();
      }
      throw new IllegalArgumentException(
          String.format("No injector was found for %s", fragment.getClass().getCanonicalName()));
    }Copy the code

If you need to implement dependency injection in fragments, you can implement it in two ways: One is the host Activity implement HasSupportFragmentInjector, another kind is HasSupportFragmentInjector Application implementation.

3.3 Other Components

Service, BroadcastReceiver, and ContentProvider are injected similarly. For convenience, Dagger.Android provides us with some wrapped component classes, which can be used directly if needed, as quoted from the official documentation below.

Because DispatchingAndroidInjector looks up the appropriate AndroidInjector.Factory by the class at runtime, a base class can implement HasActivityInjector/HasFragmentInjector/etc as well as call AndroidInjection.inject(). All each subclass needs to do is bind a corresponding @Subcomponent. Dagger provides a few base types that do this, such as DaggerActivity and DaggerFragment, If you don’t have a complicated class hierarchy. Dagger also provides a DaggerApplication for the same purpose need to do is to extend it and override the applicationInjector() method to return the component that should inject the Application.

The following types are also included:

  • DaggerService and DaggerIntentService
  • DaggerBroadcastReceiver
  • DaggerContentProvider

Note: DaggerBroadcastReceiver should only be used when the BroadcastReceiver is registered in the AndroidManifest.xml. When the BroadcastReceiver is created in your own code, prefer constructor injection instead.

4. Multi-module Combat

The above is just a brief introduction to the use of Dagger.Android, the next point will be to explain how to use Dagger to build multiple Module dependencies and realize componentization through an example. Here, separate the dependency injection for activities and fragments into a Library Module, Using the Application. ActivityLifecycleCallbacks and FragmentManager. FragmentLifecycleCallbacks monitoring, building a global dependency injection.

Here to ask if this deal, the Dagger dependencies: DaggerComponent – > AppComponent – > MainActivitySubcomponent/MainFragmentSubcomponent including: The DaggerComponent is the library injector whose scope is @singleton; AppComponent is the global injector for the main Module, its scope is @AppScope, and AppComponent is dependent on DaggerComponent, that is, the DaggerComponent top-level injector. The AppComponent is the injector to the main Module; MainActivitySubcomponent/MainFragmentSubcomponent is the Activity/fragments of the injector, the scope for @ ActivityScope / @ FragmentScope, Here is @subComponent, which inherits the hierarchy of dependencies, the next level of the AppComponent.

AppScope

  @Scope
  @Retention(RUNTIME)
  public @interface AppScope {
  }Copy the code

4.1 the Library Module

4.4.1 DaggerFragmentLifecycleCallbacks – global fragments dependency injection

Here use FragmentLifecycleCallbacks global listening fragments of life cycle, the Dagger. The Android undertake unity into management.

DaggerFragmentLifecycleCallbacks

  public class DaggerFragmentLifecycleCallbacks extends FragmentManager.FragmentLifecycleCallbacks {

      @Inject
      public DaggerFragmentLifecycleCallbacks(a) {}@Override
      public void onFragmentAttached(FragmentManager fm, Fragment f, Context context) {
          super.onFragmentAttached(fm, f, context);
          Timber.i(f.toString() + " ---> onFragmentAttached");
          AndroidSupportInjection.inject(f);//Dagger.Android Inject for Fragment
      }

      @Override
      public void onFragmentActivityCreated(FragmentManager fm, Fragment f, Bundle savedInstanceState) {
          super.onFragmentActivityCreated(fm, f, savedInstanceState);
          Timber.i(f.toString() + " ---> onFragmentActivityCreated");
      }

      @Override
      public void onFragmentDetached(FragmentManager fm, Fragment f) {
          super.onFragmentDetached(fm, f);
          Timber.i(f.toString() + " ---> onFragmentDetached"); }}Copy the code

As you can see, is managed by Dagger DaggerFragmentLifecycleCallbacks, in onFragmentAttached fragments of dependency injection () method; Several critical lifecycle callback logs were printed using Timber.

4.1.2 DaggerActivityLifecycleCallbacks – global Activity dependency injection

Used here ActivityLifecycleCallbacks global listening Activity lifecycle, the Dagger. The Android undertake unity into management.

DaggerActivityLifecycleCallbacks

  public class DaggerActivityLifecycleCallbacks implements Application.ActivityLifecycleCallbacks {
      @Inject
      DaggerFragmentLifecycleCallbacks mFragmentLifecycleCallbacks;

      @Inject
      public DaggerActivityLifecycleCallbacks(a) {}@Override
      public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
          Timber.w(activity + " ---> onActivityCreated");
          AndroidInjection.inject(activity);//Dagger.Android Inject for Activity
          if ((activity instanceof HasSupportFragmentInjector || activity.getApplication() instanceof HasSupportFragmentInjector)
                  && activity instanceof FragmentActivity) {
              ((FragmentActivity) activity).getSupportFragmentManager()
                      .registerFragmentLifecycleCallbacks(mFragmentLifecycleCallbacks, true); }}@Override
      public void onActivityStarted(Activity activity) {}@Override
      public void onActivityResumed(Activity activity) {}@Override
      public void onActivityPaused(Activity activity) {}@Override
      public void onActivityStopped(Activity activity) {}@Override
      public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}@Override
      public void onActivityDestroyed(Activity activity) {
          Timber.w(activity + " ---> onActivityDestroyed"); }}Copy the code

4.1.3 DaggerComponent – Top-level injector

DaggerComponent

  @Singleton
  @Component(modules = {AndroidInjectionModule.class,
          DaggerModule.class})
  public interface DaggerComponent {
      Application application(a);

      void inject(DaggerDelegate daggerDelegate);
  }Copy the code

DaggerModule mainly provides some global dependencies, and there is only one provideApplication() method that can add what you need.

DaggerModule

  @Module
  public class DaggerModule {
      private final Application mApplication;

      public DaggerModule(Application application) {
          mApplication = application;
      }

      @Singleton
      @Provides
      public Application provideApplication(a) {
          return this.mApplication; }}Copy the code

4.1.4 DaggerDelegate – Start injection

DaggerDelegate

  public class DaggerDelegate {
      @Inject
      DaggerActivityLifecycleCallbacks mActivityLifecycleCallbacks;

      private DaggerComponent mComponent;
      private final Application mApplication;

      public DaggerDelegate(Application application) {
          mApplication = application;
      }

      public void onCreate(a) {
          Timber.plant(new Timber.DebugTree());

          mComponent = DaggerDaggerComponent.builder()
                  .daggerModule(new DaggerModule(mApplication))
                  .build();
          mComponent.inject(this);

          mApplication.registerActivityLifecycleCallbacks(mActivityLifecycleCallbacks);
      }


      public DaggerComponent getComponent(a) {
          returnmComponent; }}Copy the code

The DaggerDelegate class is a proxy class. To overcome the Application inheritance problem, encapsulate a proxy class to manage the Library’s Dagger injection and use it in the required Module.

This completes the dependency injection structure for the Library Module.

4.2 App Module

As you can see from the previous section, there is no Application in the Library Module. It is a Library, and if you want to use it, you need to rely on it in the main Module. Again, DaggerComponent is the top-level injector, the global injector for the main AppComponent Module, and is limited to @AppScope only. First add the Library Module dependency from the previous section to app/build.gradle:

dependencies {
    //library
    implementation project(':library')

    / /...
}Copy the code

4.2.1 AppComponent – Global injector for main Module

AppComponent

  @AppScope
  @Component(dependencies = DaggerComponent.class,
          modules = {AppModule.class,
                  MainActivityModule.class,
                  MainFragmentModule.class})
  public interface AppComponent {
      void inject(MainApp mainApp);
  }Copy the code

Dependencies = daggerComponent.class AppComponent is dependent on DaggerComponent, that is, the DaggerComponent top-level injector. AppComponent is the main Module’s injector. The AppModule mainly provides some global dependencies of the main Module and can be extended by itself.

AppModule

@Module public class AppModule { private Application mApplication; public AppModule(Application application) { mApplication = application; }}Copy the code

4.2.2 MainApp – Where the real injection is

MainApp

  public class MainApp extends Application implements HasActivityInjector {
      @Inject
      DispatchingAndroidInjector<Activity> mActivityInjector;

      private DaggerDelegate mDaggerDelegate;
      private AppComponent mAppComponent;

      @Override
      public void onCreate(a) {
          super.onCreate();

          //Library dependency injection (top-level)
          mDaggerDelegate = new DaggerDelegate(this);
          mDaggerDelegate.onCreate();

          // Inject into the main Module (this Module is global)
          mAppComponent = DaggerAppComponent.builder()
                  .daggerComponent(getDaggerComponent())
                  .build();
          mAppComponent.inject(this);

      }

      public DaggerComponent getDaggerComponent(a) {
          return mDaggerDelegate.getComponent();
      }

      public AppComponent getAppComponent(a) {
          return mAppComponent;
      }

      @Override
      public AndroidInjector<Activity> activityInjector(a) {
          returnmActivityInjector; }}Copy the code

MainApp is where the real dependency injection takes place, first using the DaggerDelegate from the previous Library Module for top-level dependency injection and then for the main Module. Note that MainApp must implement the HasActivityInjector interface to perform a Dagger.Android injection.

Finally, don’t forget to specify MainApp in androidmanifest.xml.

Holdings MainActivityModule/MainFragmentModule

Examples of knowable by the first quarter: when a Subcomponent and its Builder or supertype, there is no other method can no longer need to write a Subcomponent, but by @ ContributesAndroidInjector annotations to automatically generated. So, here MainActivitySubcomponent/MainFragmentSubcomponent can be omitted; In this way, MainActivityModule/MainFragmentModule is as follows:

MainActivityModule

  @Module
  public abstract class MainActivityModule {
    @ActivityScope
    @ContributesAndroidInjector(modules = KobeModule.class)//DataModule
    abstract MainActivity contributeMainActivity(a);
  }Copy the code

MainFragmentModule

  @Module
  public abstract class MainFragmentModule {
      @FragmentScope
      @ContributesAndroidInjector(modules = JordonModule.class)//DataModule
      abstract MainFragment contributeMainFragment(a);
  }Copy the code

The KobeModule and JordonModule are still in the first section, so I won’t repeat the code. Now you can do dependency injection in your Activity/Fragment. Dagger. Adnroid injection is in the Library Module of DaggerActivityLifecycleCallbacks/DaggerFragmentLifecycleCallbacks finish, this is a global listener, Use DaggerDelegate to register in MainApp.

4.2.4 summary

Once again, the Dagger dependency of this schema:

DaggerComponent – > AppComponent – > MainActivitySubcomponent/MainFragmentSubcomponent including: The DaggerComponent is the library injector whose scope is @singleton; AppComponent is the global injector for the main Module, its scope is @AppScope, and AppComponent is dependent on DaggerComponent, that is, the DaggerComponent top-level injector. The AppComponent is the injector to the main Module; MainActivitySubcomponent/MainFragmentSubcomponent is the Activity/fragments of the injector, the scope for @ ActivityScope / @ FragmentScope, Here is @subComponent, which inherits the hierarchy of dependencies, the next level of the AppComponent.

5 concludes

The first of the two examples above shows a simple use of the Dagger.Android, which is a new pose for Dagger2.11; This eliminates the need to repeat a handwritten string of dependency injection code for each Activity/Fragment; But by implementing HasActivityInjector/HasSupportFragmentInjector interface, through the life cycle of monitoring, using AndroidInjection. Inject () automatic injection. The above is the basic Dagger level dependency of the MVVMArms framework, see MVVMArms for more details. If you have any questions, please feel free to share them. If there is something wrong in the article, please feel free to comment. Knowledge sharing will bring happiness, later I will continue to decompose the key modules of MVVMArms, if you have any questions or suggestions on MVVMArms, welcome to communicate with us.

6 Github

The source code of the above solution can be viewed on Github.

  • DaggerAndroid
  • MVVMArms

7 Related Resources

  1. Dagger 2
  2. Dagger & Android
  3. Dagger2 learning

contact

I’m Xiaobailong24, you can find me through the following platforms:

  • Making: github.com/xiaobailong…
  • Jane: www.jianshu.com/u/3dac2ad17…
  • The Denver nuggets: juejin. Cn/user / 310467…