I will soon join the new company. The new company uses Hilt, so I will familiarize myself with it in these days. Hilt is the wrapper for the Dagger, so I have to practice the Dagger.

Both of these are dependency injection frameworks, and dependency injection is based on the principle of inversion of control, which controls the execution of certain code, that is, the creation of objects, which are created by an external container, and then injected into the current object.

Dependency injection is a widely used programming principle, and following this specification can make your code more flexible, such as the following

  1. Decoupling: Object creation depends on the dependency injection container. If an object is used in many places and one day the object changes, or the implementation class changes, simply change the generation method in the container.
  2. Easy refactoring: Network frames, image frames, etc. in a project can be switched with one click through dependency injection.
  3. Easy to test: It is common to mock objects when testing. If a piece of code uses an object that has been passed in from the outside, it can be tested by passing it in, but adding an internal new object is not so convenient.

Dependency injection can be done in the following ways:

  1. Injection via constructor
  2. Injection via setter methods
  3. There are two main ways of dynamic injection through third-party libraries: runtime dynamic, and runtime dependency generation through annotations and reflection. A compile-time static scheme that generates connection-dependent code at compile time. Dagger is the second scheme used.

We’ll have to use the Dagger and then we’ll see how it works, but let’s take a look at the basic usage of the Dagger and Hilt with a little example from the authorities. This little example does the following:

LoginActivity => LoginViewPresenter => LoginRepository => LoginLocalDatasource and LoginRemoteDatasource

A LoginActivity obtains local and remote data from a LoginPresenter

Dagger in Android

Guide package

apply plugin: 'kotlin-kapt'

dependencies {
    implementation 'com.google.dagger:dagger:2.x'
    kapt 'com.google.dagger:dagger-compiler:2.x'
}
Copy the code

The @Inject annotation tells how Dagger creates the current object

We’ll create the data class and Presenter class first

class LoginPresenter @Inject constructor(
    private val loginRepository: LoginRepository
) {
    fun getData(a){
        loginRepository.getData()
    }
}
class LoginRepository @Inject constructor(
    private val localDatasource: LoginLocalDatasource,
    private val loginRemoteDatasource: LoginRemoteDatasource
){
    fun getData(a) {
        localDatasource.getLocalData()
        loginRemoteDatasource.getRemoteData()
    }
}
class LoginLocalDatasource @Inject constructor() {
    fun getLocalData(a) {
        Log.d("getData"."Here comes the local data.")}}class LoginRemoteDatasource @Inject constructor() {
    fun getRemoteData(a) {
        Log.d("getData"."Remote data is here.")}}Copy the code

The @Component annotation tells you which classes need to be injected

@Component
interface ApplicationComponent {
    fun injectLoginActivity(activity: LoginActivity)
    
    fun injectOtherctivity(activity: Otherctivit). }Copy the code

@Component acts on the interface, and its internal methods do not support polymorphism, which means that if we have multiple activities to inject, we can write similar methods.

Get the global Component Component object in application for use elsewhere

class MyApp:Application() {
    val applicationComponent = DaggerApplicationComponent.create()
}
Copy the code

Complete the object injection and use in LoginActivity

class LoginActivity:AppCompatActivity() {
    @Inject
    lateinit var loginPresenter: LoginPresenter
    override fun onCreate(savedInstanceState: Bundle?). {
        (applicationContext as MyApp).applicationComponent.injectLoginActivity(this)
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login)
        loginPresenter.getData()
    }
}
Copy the code

Note that the injection method calls need to be written before super.oncreate () to avoid Fragment recovery problems. During the recovery phase in super.onCreate(), the Activity may need access to the Fragment bound to the Activity.

Inject a loginPresenter object with the @Inject annotation and use it directly in the current activity.

what! That’s a lot of work to do to create an object, and because of that, it dissuades a lot of people,

However, due to the many advantages of the DEPENDENCY injection framework, Google has taken great pains to make sure that we can use the injection framework in our application. Dagger is difficult to use, so let’s simplify it a little bit and then Hilt appears.

Basic usage of Hilt in Android

The gradle plugin is imported into the gradle file of your project

Classpath 'com. Google. Dagger hilt - android - gradle - plugin: 2.28 alpha'Copy the code

App. Gradle was introduced

Classpath 'com. Google. Dagger hilt - android - gradle - plugin: 2.28 alpha'Copy the code

The class that gets the data is the same one that we have in the Dagger and I’m just going to skip over it and start with a different step

Add the @hiltAndroidApp annotation to the application

This annotation triggers code generation for Hilt

@HiltAndroidApp
class MyApp : Application() {}
Copy the code

Add @AndroidEntryPoint to the class you want to inject

This annotation tells Hilt where the injection point is like the following:

@AndroidEntryPoint
class LoginActivity:AppCompatActivity() {
    @Inject
    lateinit var loginPresenter: LoginPresenter
    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login)
        loginPresenter.getData()
    }
}
Copy the code

At this point we can take the loginPresenter object and operate it freely.

Dagger can be used for comparison. Hilt saves us from writing the @Component code manually. When importing the Hilt package, Hilt-android-gradle-plugin is a plugin for Gradle. Google has done some work in this plugin to automatically insert the injected code.

So now we have the basic usage of the dagger and Hilt, and you can see that Hilt is fairly simple to use.

Dagger and Hilt other uses

Dagger module injection

There is another way to create an instance of an object from the dagger module by adding the @Inject annotation to the constructor. For example, if we want to initialize an object in a third-party library, it is impossible to annotate @inject its constructor, so use this method.

@Module
class MyModule {

    @Provides
    fun httpObject(a): HttpObject{
        return HttpObject()
    }

    @Provides
    fun httpDb(a): DbObject{
        return DbObject()
    }
}
class HttpObject {
    fun doHttp(a){
        Log.i("doHttp"."doHttp method handle")}}class DbObject {
    fun doDb(a){
        Log.i("doDb"."doDb method handle")}}Copy the code

The @Module annotation tells dagger this is a Module Module, inside it with the @Provides annotation uses the @Provides annotation to tell dagger how to create an instance of an object, okay

To show the dagger that we have a custom module, we need to add modules to the @Component annotation, which is an array, we can add multiple different modules.

@Component(modules = [MyModule::class])
interface ApplicationComponent {

    fun injectLoginActivity(activity: LoginActivity)

    fun injectMainActivity(activity: MainActivity)
}
Copy the code

The official recommendation is to use the @Inject annotation whenever possible to tell the dagger how to create an object, use the Module when this annotation is not satisfied, for example we need to do some other calculation before creating an object. Or some third-party libraries cannot be injected through constructors. Because every time we need to provide an instance of a class, dagger runs the @provides annotated function

Dagger scope

The scope can restrict an instance to a specific lifecycle, for example HttpObject we want to be globally unique, we can use the dagger @Singleton annotation to decorate Component and Module, we can also customize annotations with different names, For example, @ApplicationScope

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

When using constructor injection (via @inject), add scope annotations to the class; When using the Dagger module, you add scoped annotations to the @provides method. For example, the code below

@Singleton
@Component(modules = [MyModule::class])
interface ApplicationComponent {

    fun injectLoginActivity(activity: LoginActivity)

    fun injectMainActivity(activity: MainActivity)
}
@Singleton
class LoginLocalDatasource @Inject constructor() {
    fun getLocalData(a) {
        Log.d("getData"."Here comes the local data.")}}@Singleton
    @Provides
    fun httpObject(a): HttpObject{
        return HttpObject()
    }
Copy the code

Hilt module injection

Like dagger, Hilt also provides a module to complete the injection when a single constructor cannot handle the dagger. Unlike Dagger, Hilt needs to add an @installin annotation to tell the Dagger which Android class the module needs to run in, for example:

@Module
@InstallIn(ActivityComponent::class) // To tell Hilt which Android class each module will be used in or installed in.
object MyModule {
    @Provides
    fun httpObject(a): HttpObject{
        return HttpObject()
    }
    @Provides
    fun httpDb(a): DbObject{
        return DbObject()
    }
}
Copy the code

Because Hilt is designed specifically for Android, there are several Android classes available in Hilt, and we can choose objects with different scopes according to our needs.

Hilt components The object for which the injector is oriented Create time Destruction of the timing
ApplicationComponent Application Application#onCreate() Application#onDestroy()
ActivityRetainedComponent ViewModel Activity#onCreate() Activity#onDestroy()
ActivityComponent Activity Activity#onCreate() Activity#onDestroy()
FragmentComponent Fragment Fragment#onAttach() Fragment#onDestroy()
ViewComponent View View#super() View destruction
ViewWithFragmentComponent Views with @WithFragmentBindings annotations View#super() View destruction
ServiceComponent Service Service#onCreate() Service#onDestroy()

Hilt injection interface

Hilt can also inject interfaces with the @bind annotation, for example

@Module
@InstallIn(ActivityComponent::class) 
abstract class HttpModule {

    @BindOkhttp
    @Binds
    abstract fun bindOkhttp(okhttpRequest: OkhttpRequest):IHttpRequest

    @BindVolley
    @Binds
    abstract fun bindVolley(volleyRequest: VolleyRequest):IHttpRequest

}
interface IHttpRequest {
    fun get(a)
}
class OkhttpRequest @Inject constructor(@ApplicationContext context: Context)
    :IHttpRequest{
    override fun get(a) {
        Log.d("IHttpRequest"."OkhttpRequest handle")}}class VolleyRequest @Inject constructor(@ActivityContext context: Context)
    :IHttpRequest{
    override fun get(a) {
        Log.d("IHttpRequest"."VolleyRequest handle")}}Copy the code

We have an interface and implementation classes for the interface, and with the @Sharing annotation, dagger creates the implementation class in the parameter when running and assigns values to the interface instance. We can use the example elsewhere.

We know that interfaces can be multi-implemented. When our interface has multiple implementation classes, we may need to write multiple @Cursor-modified methods in the above code. How can we distinguish which implementation class is used? Custom annotation qualifiers are provided in Hilt. These qualifiers can operate on the @Binds and @provides modifications. For example, let’s define two qualifiers

// Different qualifiers need to have different names
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class BindOkhttp(a)@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class BindVolley(a)Copy the code

You can also add annotations about which implementation class you want to use in the final use. This approach can be used when isolating third-party frameworks, such as OkHttp and Volley, above, and can be switched by modifying an annotation in MainActivity below.

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @BindOkhttp
    @Inject
    lateinit var httpRequest: IHttpRequest

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        httpRequest.get()}}Copy the code

There are also some default qualifiers predetermined in Hilt, such as @ApplicationContext and @ActivityContext in the previous code. If we need these two contexts in a method in our class, we just need to add these two annotations to use them.

So now we have all the common methods for dagger and Hilt, how does it actually do the injection

Dagger injection principle

The dagger annotation handler is imported via kapt, so we know that the dagger generates some auxiliary code to create the object at compile time through the annotation handler. Location in the/app/build/generated/source/kapt/debug folder

Let’s start with the HttpObject example from earlier

  1. First we call the initialization method in the applicationval applicationComponent = DaggerApplicationComponent.create()
  public static ApplicationComponent create(a) {
    return new Builder().build();
  }
  public ApplicationComponent build(a) {
    if (myModule == null) {
      this.myModule = new MyModule();
    }
    return new DaggerApplicationComponent(myModule);
  }
  private DaggerApplicationComponent(MyModule myModuleParam) {
    this.myModule = myModuleParam;
  }
Copy the code

Create MyModule instance of the object and stored in DaggerApplicationComponent class member variables.

  1. We then call the initialization method in MainActivity(applicationContext as MyApp).applicationComponent.injectMainActivity(this)InjectMainActivity is a method in our custom Component interface that, when clicked, now automatically has a method implemented.
 @Override
  public void injectMainActivity(MainActivity activity) { injectMainActivity2(activity); F }private MainActivity injectMainActivity2(MainActivity instance) {
    MainActivity_MembersInjector.injectHttpObject(instance, HttpModule_HttpObjectFactory.httpObject(myModule));
    return instance;
  }
Copy the code

HttpModule_HttpObjectFactory. HttpObject (myModule) can see return point in this sentence Preconditions.checkNotNullFromProvides(instance.httpObject()); Call myModule directly to httpObject(), which happens to be our custom method for creating httpObject objects, and return the object to be created.

  1. The last callMainActivity_MembersInjector.injectHttpObjectmethods
 @InjectedFieldSignature("com.yjt.daggertest.MainActivity.httpObject")
  public static void injectHttpObject(MainActivity instance, HttpObject httpObject) {
    instance.httpObject = httpObject;
  }
Copy the code

Complete the injection of httpObject, the member variable assigned to the activity.

The summary is simple: We write some auxiliary code that tells dagger how to create an object. To do so, we pass the activity object into the Dagger container, dagger takes the activity object, calls the method we wrote to create the object, and assigns the value to the activity’s member variable.

Hilt injection principle

We can see from the previous exercise that Hilt optimizes for the dagger by eliminating the need to write the Component itself and automatically generating the associated code instead.

First of all, we came to the annotation processor generates folder/app/build/generated/source/kapt/debug, will see such a file Hilt_MainActivity and MainActivity_GeneratedInjector these two classes

@Generated("dagger.hilt.android.processor.internal.androidentrypoint.ActivityGenerator")
public abstract class Hilt_MainActivity extends AppCompatActivity implements GeneratedComponentManagerHolder {
  private volatile ActivityComponentManager componentManager;

  private final Object componentManagerLock = new Object();

  private boolean injected = false;

  Hilt_MainActivity() {
    super(a); _initHiltInternal(); } Hilt_MainActivity(int contentLayoutId) {
    super(contentLayoutId);
    _initHiltInternal();
  }

  private void _initHiltInternal(a) {
    addOnContextAvailableListener(new OnContextAvailableListener() {
      @Override
      public void onContextAvailable(Context context) { inject(); }}); }@Override
  public final Object generatedComponent(a) {
    return this.componentManager().generatedComponent();
  }

  protected ActivityComponentManager createComponentManager(a) {
    return new ActivityComponentManager(this);
  }

  @Override
  public final ActivityComponentManager componentManager(a) {
    if (componentManager == null) {
      synchronized (componentManagerLock) {
        if (componentManager == null) { componentManager = createComponentManager(); }}}return componentManager;
  }

  protected void inject(a) {
    if(! injected) { injected =true;
      ((MainActivity_GeneratedInjector) this.generatedComponent()).injectMainActivity(UnsafeCasts.<MainActivity>unsafeCast(this)); }}@Override
  public ViewModelProvider.Factory getDefaultViewModelProviderFactory(a) {
    return DefaultViewModelFactories.getActivityFactory(this.super.getDefaultViewModelProviderFactory()); }}public interface MainActivity_GeneratedInjector {
  void injectMainActivity(MainActivity mainActivity);
}
Copy the code

The inject() method is called when the Context is available in the constructor of the class that inherits from AppCompatActivity. InjectMainActivity injectMainActivity has been called from MainActivity_GeneratedInjector. This method looks familiar, just like the @Component class we wrote ourselves when we used dagger.

OK, we have the class associated with OK, so when WE use dagger, Initialize injected methods such as applicationContext as (applicationContext as) are written to the activity’s onCreate method MyApp). ApplicationComponent. InjectMainActivity (this), and the Hilt is how to set it generated class MainActivity combined together with us?

Hilt gradle plugin is used to insert code into the root gradle file. This plugin is used to insert code into the file.

I now use the gradle version is version 7.0 +, to generate the file location in the/app/build/intermediates/asm_instrumented_project_classes/debug folder below. 7.0 the following in/app/build/intermediates/transforms the folder below.

public final class MainActivity extends Hilt_MainActivity {
   @Inject
   public IHttpRequest httpRequest;

   @NotNull
   public final IHttpRequest getHttpRequest(a) {
      IHttpRequest var1 = this.httpRequest;
      if(var1 ! =null) {
         return var1;
      } else {
         Intrinsics.throwUninitializedPropertyAccessException("httpRequest");
         return null; }}public final void setHttpRequest(@NotNull IHttpRequest var1) {
      Intrinsics.checkNotNullParameter(var1, "
      
       "
      ?>);
      this.httpRequest = var1;
   }

   / * *@deprecated* /
   // $FF: synthetic method
   @BindOkhttp
   public static void getHttpRequest$annotations() {
   }

   protected void onCreate(@Nullable Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      this.setContentView(2131427357);
      ((TextView)this.findViewById(2131231121)).setOnClickListener(MainActivity::onCreate$lambda-0);
      this.getHttpRequest().get();
   }

   private static final void onCreate$lambda_0/* $FF was: onCreate$lambda-0*/(MainActivity this$0, View it) {
      Intrinsics.checkNotNullParameter(this$0."this$0");
      this$0.startActivity(new Intent((Context)this$0, LoginActivity.class)); }}Copy the code

As you can see, by modifying the bytecode, we changed our MainActivity to inherit from its own generated Hilt_MainActivity. The Hilt_MainActivity constructor is also called when MainActity is created, and eventually executes the injected code when listening for the Context to be available.

OK by now you should be able to skillfully use these two frames ha ha ha