AndroidのHiltをJavaで実装してみる

# はじめに
Hiltのアルファ版が出たので調査しました。
あのわかりづらかったDagger2が非常にわかりやすくなっていました。導入の敷居がさがったと思います。そこで、「DIしたい、Hiltを使ってみたい、基本的なことを知りたい」という方に向け、サンプルアプリを作成しながら基本的なことを説明したいと思います。
なお、Dagger2からHiltへの移行方法などは記載していません。


# いきなり実装
概要等の説明は省き、いきなり実装です。DIやHiltについては[公式ページ](https://developer.android.com/training/dependency-injection?hl=ja)を参照してください。
今回作成するサンプルは、画面の中のボタンを押すとデータベースを検索し結果を表示するアプリです。

作成するクラスの構成は以下のようにします。
MainActivity --> SampleUseCase --> SampleRepository --> SampleDao --> DB

このアプリを`Hilt`を使って実装します。
データベースまわりは`Room`を使用しますが`Room`については触れません。


環境は以下のようになっています。
- Android Studio 4.0.1
- Android Virtual Device - android10.0 / 1080 x 2160

## ライブラリの追加

まずは、ルートの`build.gradle`の設定です。

```gradle

buildscript {
    dependencies {
        classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'
        // ・・・（省略)・・・
   }
}
```

次に、`app/build.gralde`の設定です。

```gradle

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

android {
    // ・・・(省略)・・・
}

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

## Applicationクラスの作成

次に、Applicationクラスを作成します。
Applicationクラスに`@HiltAndroidApp`をつけるだけです。今までは`DaggerApplication`を継承するか、`HasAndroidInjector`をimplしていましたが、HiltではアノテーションをつけるだけでOKです。

Applicationクラスを作成したら、AndroidManifest.xmlに追加します。


```java

@HiltAndroidApp
public class SampleApplication extends Application {
}

```
```xml:AndroidManifest.xml
<application
    android:name=".SampleApplication"    
    android:icon="@mipmap/ic_launcher">
    ・・・（省略）・・・            
</application>
```

今まではAppComponentを作成していましたが、Hiltでは不要になりました。Dagger2からの移行の場合はバッサリ削除します。

```java

// @Singleton
// @Component(modules={AndroidInjectionModule.class})
// public interface AppComponent extends AndroidInjector<SampleApplication> {
// 　　・・・（省略)・・・
// }
```

# Activityに注入する
Activityに`@AndroidEntryPoint` アノテーションを付けるとinjectできるようになります。
今までは、`HasAndroidInjector`のimplや `AndroidInjection.inject(this);`の実行などを行っていましたが、Hiltではアノテーションをつけるだけです。

今回のサンプルでは、MainActiviytにSampleUseCaseを注入します。

```java: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)
    }
}

```
（１） `AndroidEntryPoint` アノテーションをつけます。
（２） `Inject` アノテーションをつけます。　この`useCase` 変数にHiltによってインスタンスが注入されます。
（３）ボタン押下時に`useCase`を実行します。　コード上はSampleUseCaseはnew していないのに実行できます。Hiltによってインスタンスが生成されて注入されているからです。



次に、SampleUseCaseです。
まずは、ログを出力するだけの実装です。

```java:SampleUseCase.java

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

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

    public void execute() {
        Log.d(TAG, "実行!!");
    }
}
```


（１） コンストラクタに`Inject` アノテーションをつけます。これがないとHilt管理のオブジェクトにならず、ActivityにInjectされません。

ここまでで実際に動きます。DIができています。
非常に簡単ですね。Dagger2のときと比べると圧倒的に簡単になっています。

# Hiltモジュールの作成
上のサンプルではInjectするクラス（SampleUseCase)では、コンストラクタに`@Inject`を指定しています。しかし、`@Inject`を付与できないことがあります。たとえば、インターフェースの場合や、外部ライブラリのクラスなどです。
このような場合は`@Module` アノテーションを付与したクラス作成し、インスタンスの生成方法をHiltに知らせます。

今回のサンプルでは、SampleUseCaseにインターフェースの`SampleRepository` を呼び出すところが該当します。
SampleUseCaseクラスに実装を追加します。

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

    @Inject
    SampleRepository repository;   // インターフェース

    @Inject
    public SampleUseCase() {
    }

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

        // 実行します
        List<SampleEntity> results = repository.find();

        // 結果をログに表示      
        results.forEach(result -> {
            Log.d(TAG, result.getName());
        });
    }
```

## `Binds`を使用してインスタンスを注入する
`SampleRepository`の実装です。インターフェースと実体クラスは以下のようになります。

```java:SampleRepository.java
public interface SampleRepository {
    List<SampleEntity> find();
}
```

```java: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;
    }
}
```
（１） 実体クラスのコンストラクタには`@Inject`を付与します


これだけではInjectできません。
このインターフェースのインスタンスを生成する方法をHiltに教える必要があります。

Hiltモジュールは、`@Module`アノテーションが付けられたクラスです。Daggerのモジュールとは異なり、Hiltは`@InstallIn`アノテーションを付けて依存関係を指定します。

```java:DataModule.java
@Module                                        // ・・・ （１）
@InstallIn(ApplicationComponent.class)         // ・・・ (2)
abstract public class DataModule {

    @Binds                                     // ・・・ (3)
    public abstract SampleRepository bindSampleRepository(SampleRepositoryImpl impl);
                                                                              
}
```
（１） `@Module`アノテーションを付与しHiltモジュールクラスであることを宣言します。クラス名はなんでもよいです。
（２） このModuleの依存関係を指定します。この例では、ここで宣言したクラスたちは、アプリ内のどのクラスにでもInjectできます。この指定は以下の表のようにいろいろと指定することができます。
例えば、FragmentComponentを指定した場合は、FragmentにインジェクションできますがActivityにはインジェクションできません。今回はアプリのどのクラスにでも注入できるように、ApplicationComponentを指定します。

| コンポーネント | インジェクションの対象 |
|:--|:--|
|ApplicationComponent|Application|
|ActivityRetainedComponent|ViewModel|
|ActivityComponent|Activity|
|FragmentComponent|Fragment
|ViewComponent|View|
|ViewWithFragmentComponent|WithFragmentBindingsアノテーションが付いた View|
|ServiceComponent|Service|

（３） `Binds`アノテーションを付与し、どの実体を生成するか宣言します。メソッドの戻り値には、インターフェースを指定します。メソッドのパラメータで、生成したい実体を指定します。


## `Provides`を使用してインスタンスを注入する
Binds以外にもインスタンスの生成方法を指定することができます。
外部ライブラリはコンストラクタにインジェクションを付与できません。そのような場合には`Provides`を利用します。

今回のサンプルでは、SampleRepositoryImplがDaoを呼び出すところが該当します。
Room関連をDIするときの実装ですね。(Roomに関する説明はしません。別の記事でする予定です）

上のサンプルコードの`DataMudule.java`に追加します。

```java: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);
}
```

（１） `Provides`アノテーションを付与し、どの実体を生成するか宣言します。
メソッドの戻り値には、生成したインスタンスです。パラメータは、Hiltが管理しているインスタンスを渡すことができます。
（２）このメソッドには`Singleton`アノテーションがついています。これはスコープの設定です。
通常、Hiltはリクエストがあるたびに、毎回、新しいインスタンスを作成します。これをアノテーションを付与することにより制御することができます。今回のサンプルは、`Singleton`ですので、アプリケーションで１つのインスタンスの状態を実現します。（毎回、新しいインスタンスはつくりません）。どのクラスの注入しても同じインスタンスになります。

スコープは以下のようなものが用意されています。

| Android クラス| 生成されたコンポーネント | スコープ |
|:--|:--|:--|
|Application|ApplicationComponent|Singleton|
|View Model|ActivityRetainedComponent|ActivityRetainedScope|
|Activity|ActivityComponent|ActivityScoped|
|Fragment|FragmentComponent|FragmentScoped|
|View|ViewComponent|ViewScoped|
|WithFragmentBindings|ViewWithFragmentComponent|ViewScoped|
|Service|ServiceComponent|ServiceScoped|

例えば、今回のサンプルの`DataModule.java`のInstantRunを`ActivityComponent`の変更し、SampleDaoを`ActivityScoped`に変更すると、Activityが存続する間は同じインスタンスになります。 
SampleActivity、SampleUseCase、SampleRespositoryにDaoをInjectした場合、そのDaoはすべて同じインスタンスです。


サンプルアプリの実装に戻ります。
SampleRepositoryImplにDaoを注入して実装を完成させます。

```java: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)
    }
}
```

（１）SampleDaoを注入します。スコープは`Singleton`なので毎回同じインスタンスが注入されます。
（２）newしていませんが、Hiltによって注入されるのでNullPointerExceptionにはなりません。

## その他のコード
上記で説明していないサンプルアプリの、DaoとEntityを載せておきます。

```java:SampleEntity.java
@Entity(tableName = "sample")
public class SampleEntity implements Serializable {

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

    //setter/getter省略
}
```

```java:SampleDao.java
@Dao
public interface SampleDao {

    @Insert
    long save(SampleEntity dto);

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

# 完成！！
ここまでで、画面のボタンを押すと、ログに検索結果が表示されます。
Dagger2で必要だったものがほとんど不要となり、シンプルに実現できるようになりました。非常に簡単ですね。

# まとめ
今回のサンプルを通じてHiltのポイントを整理します。
1. Activityに`@AndroidEntryPoint`を付与する
2. Inject対象のクラスのコンストラクタに`@Inject`を忘れずにつける
3. インターフェースや他のライブラリをInject対象とするときは、`@Binds`または`@Provides`を利用する
以上です。
簡単ですね。次回はHiltを利用しViewModelを使って検索結果を画面に表示させたいと思います。
では、また！


# 参考
* Android デベロッパーガイド - [Hiltを使用した依存関係の注入](https://developer.android.com/training/dependency-injection/hilt-android?hl=ja)