What is Hilt?

Hilt is Android’s dependency injection library, which reduces the boilerplate code required to perform manual dependency injection in a project. Performing manual dependency injection requires the manual construction of each class and its dependencies and the reuse and management of dependencies with the help of the container.

How is it different from Degger?

Hilt is built on the basis of Degger, which is more scene-oriented. Hilt retains the power of Degger while simplifying the use of dependency injection by providing a container for every Android class in the project and automatically managing its life cycle. (Similar to the relationship between Retrofit and OKHTTP)

What attracts me to Hilt?

Data sharing and dependency management. To be more specific, data sharing throughout the life cycle.

@ActivityScoped
public class User {
    private String name;
    private String mood;

    @Inject
    public User(a) {
        this("matthew"."Bored");
    }

    public User(String name, String mood) {
        this.name = name;
        this.mood = mood; }... Omit unimportant codeCopy the code

Activity

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    @Inject
    lateinit var user: User

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        user.mood = "Everything is fine."}... Omit unimportant codeCopy the code

A custom view inherits from AppCompatTextView and requires data from the User object to be displayed. It’s in the Layout of the MainActivity

@AndroidEntryPoint
public class UserView extends androidx.appcompat.widget.AppCompatTextView {
    @InjectUser user; . Omit unimportant code@Override
    protected void onAttachedToWindow(a) {
        super.onAttachedToWindow();
        setText(user.getName() + "What's the mood now?"+ user.getMood()); }}Copy the code

In our scenario, we need to dynamically change the value of User in MainActivity, and then UserView displays the changed value. Public void setUser(User User){public void setUser(User User){… } Then get the userView object in the activity and pass it by calling the userView.setUser function. I’ve been using this method for years, but after hilt, it suddenly feels unelegant. What if the requirement changed and you didn’t need this User object? . A meal to delete. . If Hilt is used for dependency injection, we only need to delete the injection place @inject User User; Yes, it’s that simple. The example is still a little less than ideal

Add dependencies to Applicaton. Add @hiltAndroidApp annotation to Applicaton. If you want to add a @hiltAndroidApp annotation to Applicaton, you can add a @hiltAndroidApp annotation to it

@HiltAndroidApp
public class ExampleApplication extends Application {... }Copy the code

Add @inject to the no-parameter constructor and @activityscoped to the User class

@ActivityScoped
public class User {
    private String name;
    private String mood;

    @Inject
    public User(a) {
        this("matthew"."Bored");
    }

    public User(String name, String mood) {
        this.name = name;
        this.mood = mood; }... Omit unimportant codeCopy the code

Add @AndroidEntryPoint annotation to classes that require dependency injection and @Inject annotation to object declarations if needed

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    @Inject
    lateinit var user: User

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        user.mood = "Everything is fine."}... Omit unimportant codeCopy the code
@AndroidEntryPoint
public class UserView extends androidx.appcompat.widget.AppCompatTextView {
    @InjectUser user; . Omit unimportant code@Override
    protected void onAttachedToWindow(a) {
        super.onAttachedToWindow();
        setText(user.getName() + "What's the mood now?"+ user.getMood()); }}Copy the code

That’s it!!

How do you use the Hilt

  • Adding a dependency

First, add the hilt-Android-Gradle-plugin to your project’s root build.gradle file:

buildscript {
    ...
    dependencies {
        ...
        classpath 'com. Google. Dagger hilt - android - gradle - plugin: 2.28 alpha'}}Copy the code

Next, apply the Gradle plugin and add the following dependencies to the app/build. Gradle file:

. apply plugin:'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'

android {
    ...
}

dependencies {
    implementation Com. Google. "dagger hilt - android: 2.28 alpha." "
    kapt Com. Google. "dagger hilt - android - the compiler: 2.28 alpha." "
}
Copy the code

Hilt uses Java 8 functionality. To enable Java 8 in your project, add the following code to your app/build.gradle file:

android {
  ...
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }
}
Copy the code
  • Hilt currently supports the following Android classes:

By using @hiltAndroidApp Application () and others by using @AndroidEntryPoint Activity Fragment View Service BroadcastReceiver

@hiltAndroidApp triggers code generation for Hilt, which includes a base class for the application that acts as an application-level dependency container.

@HiltAndroidApp
public class ExampleApplication extends Application {... }Copy the code

After setting Hilt in the Application class and having an application-level component, Hilt can provide dependencies for other Android classes annotated with @AndroidEntryPoint:

@AndroidEntryPoint
public class ExampleActivity extends AppCompatActivity {... }Copy the code
  • injection

To obtain dependencies from a component, perform field injection using the @inject annotation:

@AndroidEntryPoint
public class ExampleActivity extends AppCompatActivity {

  @InjectAnalyticsAdapter analytics; . }Copy the code
  • Define Hilt bindings

In order to perform field injection, Hilt needs to know how to provide instances of the necessary dependencies from the corresponding component. One way to provide binding information to Hilt is through constructor injection. Use the @inject annotation in the class constructor to tell Hilt how to provide an instance of the class.

public class AnalyticsAdapter {

  private final AnalyticsService service;

  @Inject
  AnalyticsAdapter(AnalyticsService service) {
    this.service = service; }... }Copy the code
  • The Hilt module

The constructor given in the example above takes one argument, so we also need to tell Hilt how to get an instance of AnalyticsService. That’s easy! Add @inject to the AnalyticsService constructor. But sometimes instances cannot be injected through the constructor. This could happen for a number of reasons. For example, interfaces cannot be injected through constructors; Or types we don’t own, such as third-party libraries; Or an instance that must be built through a constructor. How to do? You need to use the Hilt Module, a class with an @Module annotation. It tells Hilt how to provide instances of certain types. You must also annotate the modules with @installIn, telling Hilt which Android class each module will be used in or installed in.

  • Inject the instance using @provides

If we don’t own the class, or if we need to use a constructor, You can create a function inside the Hilt module and add the @provides annotation. The @provides annotation tells Hilt the following: 1. The return type of the function tells Hilt which type of instance is provided 3. Will the function tell Hilt how to provide an instance of the corresponding type

@Module
@InstallIn(ActivityComponent.class)
public class AnalyticsModule {

  @Provides
  @ActivityScoped  // Share scope
  public static AnalyticsService provideAnalyticsService(
    // Potential dependencies of this type
  ) {
      return new Retrofit.Builder()
               .baseUrl("https://example.com") .build() .create(AnalyticsService.class); }}Copy the code
  • Uses @Binds to inject the interface instance

If AnalyticsService is an interface, it cannot be injected via the constructor and instead provides binding information to Hilt, creating an abstract function with an @Binding annotation within the Hilt module. The annotated function provides Hilt with the following information: 1. The return type of the function tells Hilt which instance of the interface to provide; 2. The function arguments tell Hilt which implementation to provide

public interface AnalyticsService {
  void analyticsMethods(a);
}

// Constructor-injected, because Hilt needs to know how to
// provide instances of AnalyticsServiceImpl, too.
public class AnalyticsServiceImpl implements AnalyticsService {...@InjectAnalyticsServiceImpl(...) {... }}@Module
@InstallIn(ActivityComponent.class)
public abstract class AnalyticsModule {

  @Binds
  public abstract AnalyticsService bindAnalyticsService( AnalyticsServiceImpl analyticsServiceImpl );
}
Copy the code
  • Hilt components

Each Android class from which field injection can be performed has a Hilt component associated with it. This component can be introduced in @InstallIn, and each Hilt is responsible for injecting its binding into the appropriate Android class.

Hilt components The object to which the injector is oriented
ApplicationComponent Application
ActivityRetainedComponent ViewModel
ActivityComponent Activity
FragmentComponent Fragment
ViewComponent View
ViewWithFragmentComponent View with @WithFragmentBindings annotation
ServiceComponent Service

If you inject a Module into an activity using @installIn (ActivityComponent.class), does the view under the activity get dependencies? What are the rules?

  • Component hierarchy

Once a module is installed into a component, its binding can be used as a dependency for other bindings within that component, or for other bindings in any child component under that component in the component hierarchy. It’s the downward use relationship

Note: By default, if you perform field injection in the view, the ViewComponent can use the bindings defined in the ActivityComponent. If you also need to use bindings defined in the FragmentComponent and the view is part of the Fragment, use the @WithFragmentBindings annotation with @AndroidEntryPoint.

  • The declaration period of a component

Hilt automatically creates and destroys instances of component classes based on the Android class lifecycle

Generated component Create time Destruction of the timing
ApplicationComponent Application#onCreate() Application#onDestroy()
ActivityRetainedComponent Activity#onCreate() Activity#onDestroy()
ActivityComponent Activity#onCreate() Activity#onDestroy()
FragmentComponent Fragment#onAttach() Fragment#onDestroy()
ViewComponent View#super() View destruction
ViewWithFragmentComponent View#super() View destruction
ServiceComponent Service#onCreate() Service#onDestroy()

Note: ActivityRetainedComponent still exist after configuration changes, so it’s on the first call Activity# onCreate () is created, in the last call Activity# onDestroy () is destroyed.

  • scope

If you use @activityscoped to scope AnalyticsService to An ActivityComponent, Hilt will provide the same instance of AnalyticsService for the entire lifecycle of the Activity

@Provides
  @ActivityScoped  // Share scope
  public static AnalyticsService provideAnalyticsService(
    // Potential dependencies of this type
  ) {
      return new Retrofit.Builder()
               .baseUrl("https://example.com")
               .build()
               .create(AnalyticsService.class);
  }
Copy the code
Android class Generated component scope
Application ApplicationComponent @Singleton
View Model ActivityRetainedComponent @ActivityRetainedScope
Activity ActivityComponent @ActivityScoped
Fragment FragmentComponent @FragmentScoped
View ViewComponent @ViewScoped
View with @WithFragmentBindings annotation ViewWithFragmentComponent @ViewScoped
Service ServiceComponent @ServiceScoped
  • Predefined qualifiers in Hilt

Hilt provides some custom qualifiers, such as context objects from an application or Activity that your development may require. So Hilt provides the @ApplicationContext and @ActivityContext qualifiers.

public class AnalyticsAdapter {

  private final Context context;
  private final AnalyticsService service;

  @Inject
  AnalyticsAdapter(
    @ActivityContext Context context,
    AnalyticsService service
  ) {
    this.context = context;
    this.service = service; }}Copy the code
  • Provide multiple bindings for the same type

I put this at the end. Why? Because my habit is to use this less often. Let’s say you need to get different instances of the same interface. We talked about using @Binds to inject interface instances, but now we need multiple different instances. What should we do? The return type is the same!! First, define the qualifiers that are used to annotate the @Binds or @Provides methods

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
private @interface AuthInterceptorOkHttpClient {}

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
private @interface OtherInterceptorOkHttpClient {}
Copy the code

Hilt then needs to know how to provide an instance of the type corresponding to each qualifier. The following two methods have the same return value type, but the qualifier marks them as two different bindings.

@Module
@InstallIn(ActivityComponent.class)
public class NetworkModule {

  @AuthInterceptorOkHttpClient
  @Provides
  public static OkHttpClient provideAuthInterceptorOkHttpClient( AuthInterceptor authInterceptor ) {
      return new OkHttpClient.Builder()
                   .addInterceptor(authInterceptor)
                   .build();
  }

  @OtherInterceptorOkHttpClient
  @Provides
  public static OkHttpClient provideOtherInterceptorOkHttpClient( OtherInterceptor otherInterceptor ) {
      return newOkHttpClient.Builder() .addInterceptor(otherInterceptor) .build(); }}Copy the code

Annotate a field or parameter with the appropriate qualifier during usage to inject the specific type required:

// As a dependency of another class.
@Module
@InstallIn(ActivityComponent.class)
public class AnalyticsModule {

  @Provides
  public static AnalyticsService provideAnalyticsService(
    @AuthInterceptorOkHttpClient OkHttpClient okHttpClient
  ) {
      return new Retrofit.Builder()
                  .baseUrl("https://example.com") .client(okHttpClient) .build() .create(AnalyticsService.class); }}// As a dependency of a constructor-injected class.
public class ExampleServiceImpl.{

  private final OkHttpClient okHttpClient;

  @Inject
  ExampleServiceImpl(@AuthInterceptorOkHttpClient OkHttpClient okHttpClient) {
    this.okHttpClient = okHttpClient; }}// At field injection.
@AndroidEntryPoint
public class ExampleActivity extends AppCompatActivity {

  @AuthInterceptorOkHttpClient
  @InjectOkHttpClient okHttpClient; . }Copy the code