Try implementing Android Hilt in Java

Introduction

An alpha version of Hilt is out, so I investigated it. That confusing Dagger 2 was very easy to understand. I think the threshold for introduction has been lowered. Therefore, I would like to explain the basics while creating a sample application for those who want to "DI, use Hilt, and want to know the basics". In addition, the migration method from Dagger2 to Hilt is not described.

Suddenly implemented

The explanation such as the outline is omitted, and it is implemented suddenly. Please refer to Official page for DI and Hilt. The sample created this time is an application that searches the database and displays the results when you press the button on the screen.

The structure of the class to be created is as follows. MainActivity --> SampleUseCase --> SampleRepository --> SampleDao --> DB

Implement this app using Hilt. I use Room around the database, but I don't mention Room.

The environment is as follows.

Add library

First, set the root build.gradle.


buildscript {
    dependencies {
        classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'
        //···(abridgement)・ ・ ・
   }
}

Next is the setting of ʻapp / build.gradle`.


apply plugin: 'dagger.hilt.android.plugin'

android {
    //・ ・ ・(abridgement)・ ・ ・
}

dependencies {
    implementation "com.google.dagger:hilt-android:2.28-alpha"
    annotationProcessor "com.google.dagger:hilt-android-compiler:2.28-alpha"
    //・ ・ ・(abridgement)・ ・ ・
}

Creating an Application class

Next, create the Application class. Just add @HiltAndroidApp to the Application class. Until now, it inherited DaggerApplication or impliedHasAndroidInjector, but in Hilt it is OK just to add annotations.

After creating the Application class, add it to AndroidManifest.xml.


@HiltAndroidApp
public class SampleApplication extends Application {
}

AndroidManifest.xml


<application
    android:name=".SampleApplication"    
    android:icon="@mipmap/ic_launcher">
···(abridgement)···
</application>

I used to create AppComponent, but Hilt no longer needs it. If you are migrating from Dagger2, delete it.


// @Singleton
// @Component(modules={AndroidInjectionModule.class})
// public interface AppComponent extends AndroidInjector<SampleApplication> {
//···(abridgement)・ ・ ・
// }

Inject into Activity

You can inject by annotating Activity with @AndroidEntryPoint. Until now, Impl of HasAndroidInjector and ʻAndroidInjection.inject (this);` were executed, but in Hilt, it is just annotated.

In this sample, we will inject SampleUseCase into MainActiviyt.

MainActivity.java



@AndroidEntryPoint     //・ ・ ・(1)
public class MainActivity extends AppCompatActivity {

    @Inject            //・ ・ ・(2)
    SampleUseCase useCase; 

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

        Button button = findViewById(R.id.execute);
        button.setOnClickListener(v -> useCase.execute());   //・ ・ ・(3)
    }
}

(1) ʻAdd the AndroidEntryPoint annotation. (2) Add the ʻInject annotation. An instance is injected by Hilt into this ʻuseCase variable. (3) Execute ʻuseCase when the button is pressed. In the code, SampleUseCase can be executed even though it is not new. This is because Hilt has instantiated and injected it.

Next is the SampleUseCase. First, the implementation just outputs the log.

SampleUseCase.java



public class SampleUseCase {
    private static String TAG = SampleUseCase.class.getName();

    @Inject    //・ ・ ・(1)
    public SampleUseCase() {
    }

    public void execute() {
        Log.d(TAG, "Run!!");
    }
}

(1) Annotate the constructor with ʻInject`. Without it, it will not be a Hilt-managed object and will not be injected into the Activity.

It actually works up to this point. DI is ready. It's very easy. It's overwhelmingly easier than with Dagger2.

Creating a Hilt module

In the above sample, in the class to inject (SampleUseCase), @Inject is specified in the constructor. However, it may not be possible to grant @Inject. For example, for interfaces or classes in external libraries. In such a case, create a class with @Module annotation and let Hilt know how to create an instance.

In this sample, the place where you call the interface SampleRepository for SampleUseCase is applicable. Add an implementation to the SampleUseCase class.

SampleUseCase.java


public class SampleUseCase {
    private static String TAG = SampleUseCase.class.getName();

    @Inject
    SampleRepository repository;   //interface

    @Inject
    public SampleUseCase() {
    }

    public void execute() {
        Log.d(TAG, "Run!!");

        //I will do it
        List<SampleEntity> results = repository.find();

        //Display results in log
        results.forEach(result -> {
            Log.d(TAG, result.getName());
        });
    }

Inject instances using Binds

An implementation of SampleRepository. The interface and entity class are as follows.

SampleRepository.java


public interface SampleRepository {
    List<SampleEntity> find();
}

SampleRepositoryImpl.java


public class SampleRepositoryImpl implements SampleRepository {
    public static final String TAG = SampleRepositoryImpl.class.getName();

    @Inject                           //・ ・ ・(1)
    public SampleRepositoryImpl() {
    }

    public List<SampleEntity> find() {
        Log.d(TAG, "find!");
        return null;
    }
}

(1) Add @Inject to the constructor of the entity class.

This alone cannot be injected. I need to teach Hilt how to instantiate this interface.

The Hilt module is a class annotated with @Module. Unlike Dagger's modules, Hilt annotates @InstallIn to specify dependencies.

DataModule.java


@Module                                        //・ ・ ・ (1)
@InstallIn(ApplicationComponent.class)         //・ ・ ・(2)
abstract public class DataModule {

    @Binds                                     //・ ・ ・(3)
    public abstract SampleRepository bindSampleRepository(SampleRepositoryImpl impl);
                                                                              
}

(1) Add the @Module annotation and declare that it is a Hilt module class. The class name can be anything. (2) Specify the dependency of this Module. In this example, the classes declared here can be injected into any class in your app. This specification can be specified in various ways as shown in the table below. For example, if you specify FragmentComponent, you can inject into Fragment but not Activity. This time, specify ApplicationComponent so that it can be injected into any class of the app.

component Target of injection
ApplicationComponent Application
ActivityRetainedComponent ViewModel
ActivityComponent Activity
FragmentComponent Fragment
ViewComponent View
ViewWithFragmentComponent View with WithFragmentBindings annotation
ServiceComponent Service

(3) Add Binds annotation and declare which entity to generate. Specify the interface as the return value of the method. In the method parameter, specify the entity you want to generate.

Inject an instance using Provides

In addition to Binds, you can specify how to create an instance. External libraries cannot give injections to constructors. In such a case, use Provides.

In this sample, this is where SampleRepositoryImpl calls Dao. It is an implementation when DI related to Room. (I will not explain about Room. I will do it in another article)

Add it to DataMudule.java in the sample code above.

DataModule.java


@Module
@InstallIn(ApplicationComponent.class)
abstract public class DataModule {

    @Provides                                           //・ ・ ・(1) 
    @Singleton                                          //・ ・ ・(2)
    public static SampleDatabase provideDb(Application context) {
        return Room.databaseBuilder(context.getApplicationContext(), SampleDatabase.class, "sample.db")
                .addCallback(SampleDatabase.INITIAL_DATA)
                .allowMainThreadQueries()
                .build();
    }

    @Provides                                           //・ ・ ・(3)
    @Singleton                                         
    public static SampleDao provideSampleDao(SampleDatabase db) {
        return db.getSampleDao();
    }


    @Binds
    public abstract SampleRepository bindSampleRepository(SampleRepositoryImpl impl);
}

(1) Add Provides annotation and declare which entity to generate. The return value of the method is the created instance. The parameter can be passed an instance managed by Hilt. (2) This method is annotated with Singleton. This is the scope setting. Hilt typically creates a new instance every time there is a request. This can be controlled by annotating it. Since this sample is Singleton, the state of one instance is realized in the application. (I don't create a new instance every time). Injecting any class will result in the same instance.

The following scopes are available.

Android class Generated component scope
Application ApplicationComponent Singleton
View Model ActivityRetainedComponent ActivityRetainedScope
Activity ActivityComponent ActivityScoped
Fragment FragmentComponent FragmentScoped
View ViewComponent ViewScoped
WithFragmentBindings ViewWithFragmentComponent ViewScoped
Service ServiceComponent ServiceScoped

For example, if you change InstantRun of DataModule.java of this sample to ʻActivityComponent and change SampleDao to ʻActivityScoped, it will be the same instance for the life of Activity. If you inject Dao into SampleActivity, SampleUseCase, SampleRespository, the Dao are all the same instance.

Return to the sample app implementation. Inject Dao into SampleRepositoryImpl to complete the implementation.

SampleRepositoryImpl.java


public class SampleRepositoryImpl implements SampleRepository {
    public static final String TAG = SampleRepositoryImpl.class.getName();

    @Inject                    //・ ・ ・(1)
    SampleDao dao; 

    @Inject
    public SampleRepositoryImpl() {
    }

    public List<SampleEntity> find() {
        Log.d(TAG, "find!");
        return dao.find();     //・ ・ ・(2)
    }
}

(1) Inject Sample Dao. Since the scope is Singleton, the same instance is injected every time. (2) Although it is not new, it does not cause a NullPointerException because it is injected by Hilt.

Other code

Here are Dao and Entity of the sample application not explained above.

SampleEntity.java


@Entity(tableName = "sample")
public class SampleEntity implements Serializable {

    @PrimaryKey
    @NonNull
    private String code;
    private String name;

    //setter/getter omitted
}

SampleDao.java


@Dao
public interface SampleDao {

    @Insert
    long save(SampleEntity dto);

    @Query("select * from sample")
    List<SampleEntity> find();
}

Complete! !!

At this point, when you press the button on the screen, the search results will be displayed in the log. What was needed for Dagger2 is almost unnecessary, and it can be realized simply. It's very easy.

Summary

I will organize the points of Hilt through this sample.

  1. Give Activity @ AndroidEntryPoint
  2. Don't forget to add @Inject to the constructor of the class to be injected.
  3. Use @Binds or @ Provides when injecting interfaces and other libraries. that's all. It's easy. Next time, I would like to use Hilt and use ViewModel to display the search results on the screen. see you!

reference

Recommended Posts

Try implementing Android Hilt in Java
Try implementing GraphQL server in Java
It's late! Try implementing Android Notification in Java (Beginner)
It's late! Try implementing Android Work Manager in Java (Beginner)
Try using RocksDB in Java
Try calling JavaScript in Java
Try developing Spresense in Java (1)
Try functional type in Java! ①
Try implementing asynchronous processing in Azure
Try implementing signature verification for elliptic curve cryptography in Java
Try running Selenuim 3.141.59 in eclipse (java)
Try an If expression in Java
Try running AWS X-Ray in Java
Try to implement Yubaba in Java
Java: Try implementing your own comma-separated formatter
Try to solve Project Euler in Java
Try to implement n-ary addition in Java
Try using the Stream API in Java
Try using JSON format API in Java
Try calling the CORBA service in Java 11+
Try making a calculator app in Java
Java to C and C to Java in Android Studio
Partization in Java
Try Java 8 Stream
Changes in Java 11
Rock-paper-scissors in Java
Pi in Java
Roughly try Java 9
FizzBuzz in Java
Try using Firebase Cloud Functions on Android (Java)
[AWS IoT] Implementing Authorizing Direct Calls in Java [Java]
Try scraping about 30 lines in Java (CSV output)
[Android / Java] Operate a local database in Room
Try to create a bulletin board in Java
Second decoction: Try an If expression in Java
Try using Sourcetrail (win version) in Java code
Try using GCP's Cloud Vision API in Java
Try using Sourcetrail (macOS version) in Java code
Try communication using gRPC on Android + Java server
Difficulties when implementing Alarm Manager in Android Studio
Try using the COTOHA API parsing in Java
Represents "next day" and "previous day" in Java / Android
[java] sort in list
Read JSON in Java
Interpreter implementation in Java
Make Blackjack in Java
[Android / Java] Screen transition and return processing in fragments
Try calling synchronized methods from multiple threads in Java
Rock-paper-scissors app in Java
Constraint programming in Java
Put java8 in centos7
NVL-ish guy in Java
Combine arrays in Java
"Hello World" in Java
Callable Interface in Java
Try implementing the Eratosthenes sieve using the Java standard library
Comments in Java source
Azure functions in java
Try LetCode in Ruby-TwoSum
Format XML in Java
Simple htmlspecialchars in Java