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.
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.
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)・ ・ ・
}
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)・ ・ ・
// }
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.
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());
});
}
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.
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.
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();
}
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.
I will organize the points of Hilt through this sample.
@ AndroidEntryPoint
@Inject
to the constructor of the class to be injected.@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!Recommended Posts