Gunosy Tech Blog

Gunosy Tech Blogは株式会社Gunosyのエンジニアが知見を共有する技術ブログです。

Android Hilt 導入

こんにちは。Android アプリ開発担当の nagayama(@nagayan_dev)です。

今回私が担当している「auサービスToday」では、これまで Jetpack Compose ・マルチモジュール・ Coroutines を導入してきました。今回は Hilt を導入したため、その対応内容と結果についてお話しできたらと思います。

「auサービスToday」はすでに Dagger を導入済みであるため、Dagger を Hilt にバージョンアップしていく方針となります。

Hilt とは

Hilt は Android 用の依存関係インジェクションライブラリで、Dagger の上に構築されています。Dagger は今まで分かりづらく、学習コストが高いと言われてきました。Hilt はそれを解決すべく作成されたライブラリになります。

developer.android.com

Hilt を日本語にすると刀の「柄」になります。Dagger が「短剣」に対してこの名前が付けられたと ChatGPT 先生が教えてくれました。


Dagger と Hilt の比較

Dagger から Hilt にするメリットは下記の 2 点にあると思います。

① 簡単に実装できる

そもそも Hilt は Dagger の上に構築されたライブラリで、Dagger をより簡単に実装できるようになります。Android と一緒に開発されているため Jetpack ライブラリと親和性があり、ViewModel や Compose 等での利用が容易になります。

② テストコードが簡単に書ける

Dagger でのテストはボイラープレートと Dagger の設定に手間がかかります。ですが Hilt ではテスト用のアノテーションを使用するだけで、依存性の注入をテストすることができます。今後「auサービスToday」でのテストコードを拡張していこうと考えていたため、今回を機に Hilt でテストコードが簡単に作成できればと思いました。


対応内容

Android Developers では、Dagger から Hilt にマイグレーションをする Codelab を用意しています。こちらを参考に導入を行なっていきます。

developer.android.com

おおまかな対応内容は下記になります。

① ライブラリ導入

ルートプロジェクトの build.gradle ファイルに下記を追記します

plugins {
    id 'com.google.dagger.hilt.android' version '$hilt_version' apply false
}

また使用する module の build.gradle ファイルに下記を追記します

plugins {
    id 'com.google.dagger.hilt.android'
}
dependencies {
  implementation "com.google.dagger:hilt-android:$hilt_version"
  kapt "com.google.dagger:hilt-compiler:$hilt_version"
}

② Application クラスの対応

@HiltAndroidApp のアノテーションを付与します。

@HiltAndroidApp
class MainApplication : Application() {

③ Viewmodel クラスの対応

@HiltViewModel アノテーションの付与します。

@HiltViewModel
class MainViewModel @Inject constructor(〜) : ViewModel() {

④ Activity / Fragment クラスの対応

@AndroidEntryPoint アノテーションの付与します。ViewModel は viewModels() を使用して取得します。 また依存性の注入をしていた inject 処理は不要になったため削除します。

@AndroidEntryPoint
class MainFragment : Fragment() {
  
  val viewModel: MainViewModel by viewModels()
  
  override fun onAttach(context: Context) {
        // AndroidSupportInjection.inject(this) <- 削除

⑤ Module クラスの修正

各 Module クラスにて、@InstallIn でインジェクションの対象ごとに Component を指定します。

Hilt Component インジェクションの対象
SingletonComponent Application
ActivityComponent Activity
FragmentComponent Fragment
ViewComponent View
ServiceComponent Service

また、引数の Context@ApplicationContext 等のアノテーションで付与します。

下記はアプリ全体で使用するため、 @InstallInSingletonComponent を指定し、Context は @ApplicationContext を付与しています。

@Module
@InstallIn(SingletonComponent::class)
internal object RepositoryModule {
    @Singleton
    @Provides
    fun provideMainRepository(@ApplicationContext context: Context): MainRepository = MainRepositoryImpl(context)

⑥ 不要になった処理・クラスの削除

Fragment や ViewModel 等は Module クラスを作成して、Dagger の定義の管理をしていました。Hilt ではその定義が不要となったため削除します。

下記のような Module 定義を削除しました。

@Module
abstract class ViewModelModule {

    @Binds
    @IntoMap
    @ViewModelKey(MainViewModel::class)
    abstract fun bindMainViewModel(mainViewModel: MainViewModel): ViewModel


遭遇した問題

問題 1 . Application クラスはルートモジュールでないと @HiltAndroidApp を付与することができない

対応中にビルドエラーが出ました

Application class annotated with @HiltAndroidApp has to be defined in an android application project

原因としては、モジュール分割対応で Application クラスを別モジュールに移動させてしまっていました。このタイミングで気づくとは・・

ルートモジュールに Application クラスを配置して解消しました。

問題 2 . Fragment の ViewModel 参照は onCreate 以降

画面表示時に強制終了しました。

IllegalStateException: You can consumeRestoredStateForKey only after super.onCreate of corresponding component

Fragment の onAttach で ViewModel にイベントを設定する処理を行なっていました。ViewModel は onCreate 前では ViewModel が作成されていないため、onAttach ではエラーになってしまいます。

処理を onCreate に移動し、エラーを解消しました。

override fun onAttach(context: Context) {
  viewModel.event = 〜

から次のように変更しました。

override fun onCreate(savedInstanceState: Bundle?) {
  viewModel.event = 〜


結果

Hilt 導入を終えてもっとも感じたことは、新規画面の追加実装が簡単になりました。特に忘れがちな Fragment や ViewModel の Module 定義が不要となったことが大きく、ストレスフリーに開発ができるようになりました。

新規画面の実装場合

対応前 対応後
Fragment を作成する Fragment を作成する
FragmentModule に定義を追加する ※ 省略
ViewModel を作成する ViewModel を作成する
ViewModelModule に定義を追加する ※ 省略
UseCase / Repositoryを作成する UseCase / Repositoryを作成する

次は Hilt でテストを実装するぞ!