0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Android/Jetpack Compose】もうViewModelFactoryは書かない!Hilt導入でDIを自動化する詳細設計ガイド

Posted at

「またViewModelFactoryを作るのか…」「画面が増えるたびに、このお決まりのコードを書くのが面倒だな…」

Jetpack Composeで開発をしていると、そんな風に感じたことはありませんか?

**依存性注入(DI)**は、モダンなアプリ開発に不可欠な設計パターンですが、手動での実装は定型コード(ボイラープレート)の温床になりがちです。

この記事では、その面倒な手作業をGoogle推奨のDIライブラリであるHiltに任せ、DIを完全に自動化するための詳細な設計と手順を解説します。このリファクタリングを終えれば、あなたのアプリの基盤はより堅牢になり、機能追加のスピードは格段に向上するでしょう。他の開発者が見ても再現できるように、「なぜそうするのか」という思考の背景と、具体的なコードをステップ・バイ・ステップで詳細に解説します。


1. なぜHiltなのか? (Before & After)

まず、なぜこのリファクタリングを行うのか、目的を明確にしましょう。

Before: 手動DI(ViewModelFactory)の問題点

現在の私たちのアプリは、ViewModelFactoryという「部品の組み立て説明書」を画面ごとに手作りしています。

  • 問題点:
    • 画面やViewModelが増えるたびに、新しいFactoryを作る手間がかかる。
    • SavedStateHandleのように、ViewModelが必要とする部品が増えると、Factoryの実装が複雑になる。
    • 単体テストの際に、部品の差し替え(モック化)が少し面倒。

After: Hiltによる自動DIのメリット

Hiltは、いくつかの**アノテーション(@から始まる魔法の印)**を付けるだけで、DIに関する面倒な作業をすべて裏側で自動的に行ってくれるライブラリです。

  • メリット:
    • ViewModelFactory完全に不要になる。
    • @HiltViewModelと付けるだけで、ViewModelの生成とライフサイクル管理を自動化。
    • @Injectと付けるだけで、必要な部品(例: Repository)を自動で注入してくれる。
    • コードが劇的にシンプルになり、開発者はビジネスロジックの実装に集中できる。

今日の目標は、この**「手作業からの解放」**を実現することです!


2. Hilt導入の詳細設計(ステップ・バイ・ステップ)

それでは、具体的な実装手順を詳細に見ていきましょう。

Step 1: Hiltプラグインとライブラリの導入

目的: Hiltを動作させるために必要な万能工具を、プロジェクトのGradleファイルにセットアップする。

作業内容:

  1. build.gradle.kts (プロジェクトレベル) を開きます。
    • plugins ブロックにHiltのプラグインを追加します。これはプロジェクト全体のおまじないです。
// build.gradle.kts (Top-level)
plugins {
    // ...
    id("com.google.dagger.hilt.android") version "2.51.1" apply false // ★追加
}
  1. app/build.gradle.kts (モジュールレベル) を開きます。
    • plugins ブロックに、hilt-androidkspの2つのプラグインを追加します。
    • dependencies ブロックに、Hilt本体と、コードを自動生成してくれるcompilerの2つのライブラリを追加します。
// app/build.gradle.kts
plugins {
    // ...
    id("com.google.dagger.hilt.android") // ★追加
    id("com.google.devtools.ksp") // ★追加
}

dependencies {
    // ...
    implementation("com.google.dagger:hilt-android:2.51.1") // ★追加
    ksp("com.google.dagger:hilt-compiler:2.51.1") // ★追加

    // ViewModelをHiltで扱うために必要
    implementation("androidx.hilt:hilt-navigation-compose:1.2.0") // ★追加
}
  1. Android Studioの上部に表示される「Sync Now」をクリックし、プロジェクトに変更を反映させます。

Step 2: Applicationクラスへのアノテーション付与

目的: Hiltに「このアプリは僕がDIを管理するからね!」と教えるための、アプリの起動スイッチを入れる。

作業内容:

MemoApplication.ktを開き、クラス宣言の上に@HiltAndroidAppというアノテーションを一行追加します。

// MemoApplication.kt
package com.example.simplememoapp_android

import android.app.Application
import dagger.hilt.android.HiltAndroidApp // ★インポート

@HiltAndroidApp // ★この一行を追加!
class MemoApplication : Application() {
    // このクラスの中身は後で空になる
}

Step 3: DIモジュールの作成

目的: RepositoryDatabaseといった部品の**作り方(レシピ)**をHiltに教えるための「設計図(モジュール)」を作成する。

作業内容:

  1. com.example.simplememoapp_androidパッケージの下にdiという新しいパッケージを作成します。
  2. diパッケージの中にAppModule.ktという新しいファイルを作成し、以下のコードを記述します。
// di/AppModule.kt (新規作成)
package com.example.simplememoapp_android.di

import android.app.Application
import androidx.room.Room
import com.example.simplememoapp_android.data.local.AppDatabase
import com.example.simplememoapp_android.data.local.dao.MemoDao
import com.example.simplememoapp_android.data.repository.MemoRepository
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class) // アプリの生存期間中、インスタンスが共有されることを示す
object AppModule {

    @Provides
    @Singleton // このレシピで作られるインスタンスはアプリ内で常に1つだけ(シングルトン)
    fun provideAppDatabase(app: Application): AppDatabase {
        return Room.databaseBuilder(
            app,
            AppDatabase::class.java,
            "memo_database"
        )
            .addMigrations(AppDatabase.MIGRATION_1_2)
            .build()
    }

    @Provides
    @Singleton
    fun provideMemoDao(db: AppDatabase): MemoDao {
        return db.memoDao()
    }

    @Provides
    @Singleton
    fun provideMemoRepository(dao: MemoDao): MemoRepository {
        return MemoRepository(dao)
    }
}

【解説】

  • @Module: 「これは部品の作り方を教える設計図ですよ」という印。
  • @InstallIn: この設計図が、どの範囲で有効か(今回はアプリ全体)を指定。
  • @Provides: 「この関数が、部品の具体的な作り方(レシピ)ですよ」という印。
  • @Singleton: このレシピで作った部品は、アプリ内で使い回す(一つしか作らない)ことを指定。

Step 4: ViewModelのリファクタリング

目的: Hiltの魔法@HiltViewModelを使い、手作りしてきたViewModelFactoryすべて削除する。

作業内容:

  1. MemoListViewModel.ktMemoDetailViewModel.ktを開き、アノテーションを修正します。
  2. ui/viewmodelパッケージから、MemoListViewModelFactory.ktMemoDetailViewModelFactory.ktの2つのファイルを右クリックしてDeleteします。 さようなら、手作業!
// ui/viewmodel/MemoListViewModel.kt (修正)
import androidx.lifecycle.ViewModel
import com.example.simplememoapp_android.data.repository.MemoRepository
import dagger.hilt.android.lifecycle.HiltViewModel // ★インポート
import javax.inject.Inject // ★インポート

@HiltViewModel // ★アノテーションを変更
class MemoListViewModel @Inject constructor( // ★@Inject constructorを追加
    private val repository: MemoRepository
) : ViewModel() {
    // ... ViewModelの中身は一切変更なし! ...
}

MemoDetailViewModelも同様に@HiltViewModel@Inject constructorを付けて修正します。SavedStateHandleはHiltが自動で用意してくれるので、Factoryは不要です。


Step 5: UI層の修正と最終クリーンアップ

目的: UI(Composable)が、Hilt経由でViewModelを呼び出すように変更し、不要になった古いコードを掃除して完了。

作業内容:

  1. MemoListScreen.ktMemoDetailScreen.ktを開き、viewModel()の呼び出しをhiltViewModel()に修正します。
// ui/screen/MemoListScreen.kt (修正の一部)
import androidx.hilt.navigation.compose.hiltViewModel // ★インポートを変更
import androidx.compose.runtime.Composable
import androidx.navigation.NavController
import com.example.simplememoapp_android.ui.viewmodel.MemoListViewModel

@Composable
fun MemoListScreen(navController: NavController) {

    // ▼▼▼ この部分はもう不要なので、完全に削除する! ▼▼▼
    // val application = LocalContext.current.applicationContext as MemoApplication
    // val repository = application.repository
    // val viewModel: MemoListViewModel = viewModel(
    //     factory = MemoListViewModelFactory(repository)
    // )
    // ▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲

    // ★★★ ViewModelの取得が、この一行だけで完了! ★★★
    val viewModel: MemoListViewModel = hiltViewModel()

    // ... 以下は変更なし ...
}

MemoDetailScreen.ktも同様に、hiltViewModel()を使ってMemoDetailViewModelを取得するように修正します。

  1. MemoApplication.ktを再度開き、手動でRepositoryを生成していたコードを完全に削除します。
MemoApplication.kt (最終版)
package com.example.simplememoapp_android

import android.app.Application
import dagger.hilt.android.HiltAndroidApp

@HiltAndroidApp
class MemoApplication : Application() {
    // 中身は空っぽでOK!
    // Hiltがすべてを裏側で管理してくれます。
}

まとめ

以上で、Hiltの導入リファクタリングは完了です!

アプリを実行して、以前と全く同じように動作することを確認してみてください。見た目は変わりませんが、内部の構造は格段にモダンで、拡張しやすくなりました。

手作りのViewModelFactoryから卒業した今、あなたはより本質的な機能開発に集中できます。

次のステップ、ネットワーク通信機能の実装が、もう目の前ですね!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?