【正社員】還元率83%【フリーランス】マージン一律5万円で案件をご紹介させていただきます。 詳細はこちら

【Android】<Kotlin>Hiltで外部ライブラリを注入する方法|@Module + @Providesの使い方を基礎から解説

【Android】<Kotlin>Hiltで外部ライブラリを注入する方法|@Module + @Providesの使い方を基礎から解説
すだ

みなさまこんにちは〜!
メモリアインクのすだです。

今回は、Hiltというライブラリを使ったDI(依存性注入)について、詳しく解説していきます!

この記事を読んでわかること…
・Hiltを使った外部ライブラリのDIについて
@Module@Provides@InstallInなどのHiltアノテーションについて

DI(依存性注入)やHilt(DIライブラリ)の基本については、以下の記事でそれぞれ解説しておりますので、ぜひ一緒にご覧ください。

目次

環境

  • Kotlin (ver 1.9.0)
  • Android Studio (Giraffe | 2022.3.1 Patch 3)

外部ライブラリのDI

外部ライブラリを手動で注入するシーンとはどういう時か

外部ライブラリとは、例えば以下のようなものを挙げられます。

・Retrofit
・OkHttpClient
・Gson
・SharedPreferences
・RoomDatabase

たとえば、 Retrofit(API通信のライブラリ)を使いたいとき、以下のように書きます。

val retrofit = Retrofit.Builder()
    .baseUrl("https://api.example.com/")
    .build()

val repository = ApiRepository(retrofit)

上記のように、作成した Retrofit インスタンスを Repository に引数として渡すことで、
Repository 内で実際の通信処理を行う、といった設計はよく使われます。

このように、”必要なオブジェクト(依存)を手動で渡す設計は、「依存性を注入している」状態”と言えます。

Hiltを使った外部ライブラリのDI

手動で行っていたDIを、Hiltを使って自動化することができます。

val retrofit = Retrofit.Builder()
    .baseUrl("https://api.example.com/")
    .build()

このような外部ライブラリのインスタンスは、
・自分で定義していない(ソースを変更できない)
・引数や初期化が複雑
という理由で、Hiltでよく使う@Inject constructor() を使った自動注入には対応できません。

そこで必要になるのが @Module + @Provides のパターンです。

@Module…Hiltに「このクラスでは依存の作り方を定義するよ」と伝える
@Provides…「この関数で生成したインスタンスを使ってください」と指示する

これらのHiltアノテーションを使って、外部ライブラリのDIを実現していきます。


その前に、
Retrofitのインスタンスを作成する処理を object として定義しておきたいと思います。▼

object NetworkModule {
    fun provideRetrofit(): Retrofit {
        return Retrofit.Builder()
            .baseUrl("https://api.example.com/")
            .build()
    }
}

object(シングルトン)として定義しておくことで、インスタンス化されずにすみ、メモリ効率が良くなります。
つまり、毎回インスタンスを作成せずに済むため、パフォーマンス面でも有利になります。


それではHiltアノテーションを使って自動DIを実現してみます。

まず、objectから▼

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

    @Provides
    @Singleton
    fun provideRetrofit(): Retrofit {
        return Retrofit.Builder()
            .baseUrl("https://api.example.com/")
            .build()
    }
}

@Module

このアノテーションで、「依存の提供元です」という宣言をします。

@InstallIn(SingletonComponent::class)

このアノテーションで、「このModuleの提供する依存は、どの範囲で使うのか?」 を指定します。
ここで使っているSingletonComponent は、アプリ全体で1つだけ共有されるスコープ(範囲)です。
(他にもActivityComponent = Activity内で使う依存やFragmentComponent = Fragment内で使う依存 などあります)

つまり、今回は「このModuleの提供する依存はアプリ全体で使われるよ」とHiltに伝える設定ということになります。

@Provides

このアノテーションで、「この関数は依存を提供する関数です」という宣言をします。
@Provides が付いていないと、Hiltはその関数を無視します。)

@Singleton

このアノテーションで、「この依存はアプリ全体で1つのインスタンスだけ使ってください」という宣言をします。
Hiltが最初に1回だけ 自動的にprovideRetrofit() を実行して、インスタンスを保存し、以降はそれを使い回します。
(これがない場合、provideRetrofit() は呼ばれるたびに毎回新しいインスタンスを返す可能性があります。)

object内の実装が完了したら、次はRepositoryです。
コンストラクタに@Injectをつけてください▼

class ApiRepository @Inject constructor(
    private val retrofit: Retrofit
) {
    fun fetchData(): String {

        // 通常は retrofit.create(ApiInterface::class.java).getSomething() のように使いますが、
        // 今回は仮の処理で表現します

        return "通信処理をここに書く"
    }
}

そして、このAPI通信をActivity→ViewModelから呼んでみます。▼

@HiltViewModel
class ApiViewModel @Inject constructor(
    private val repository: ApiRepository
) : ViewModel() {

    fun loadData(): String {
        return repository.fetchData()
    }
}

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    private val viewModel: ApiViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val message = viewModel.getMessage()
        println("取得結果: $message")
    }
}

流れの図としては以下のようになります。

MainActivity 起動
  ↓
@AndroidEntryPoint により Hilt が ViewModel を注入
  ↓
ApiViewModel に @Inject constructor がある → Repository が必要
  ↓
ApiRepository に @Inject constructor がある → Retrofit が必要
  ↓
Hilt「Retrofit が必要…?よし、@Provides の provideRetrofit() を呼んで作るぞ」

今回は、Retrofitのインスタンスは1つだけしか定義していないため、
Hiltが自動的に NetworkModuleprovideRetrofit() を見つけて実行してくれました。
そのため、特別な指定をしなくても問題なく依存を注入することができました。

しかし、以下のように Retrofitのインスタンスが複数ある場合は、
@Named アノテーションを使って、どのインスタンスを使うかを識別する必要があります。

@Provides
@Singleton
@Named("apiA")
fun provideApiARetrofit(): Retrofit = Retrofit.Builder()
    .baseUrl("https://apiA.com/")
    .build()

@Provides
@Singleton
@Named("apiB")
fun provideApiBRetrofit(): Retrofit = Retrofit.Builder()
    .baseUrl("https://apiB.com/")
    .build()

そして、使う側では:

class MyRepository @Inject constructor(
    @Named("apiA") private val retrofit: Retrofit
)

このようにコンストラクタ引数に @Named("apiA") を指定することで、
Hiltは適切な Retrofit インスタンスを判断し、自動で注入してくれるようになります。

まとめ

おつかれさまでした。いかがでしたでしょうか!

このあたり少々複雑で理解するのに時間がかかるかもしれませんが、

・Hiltでは、外部ライブラリには @Module + @Provides を使う
・その定義は object で書くのがシンプルでおすすめ

というのをまず理解するだけで大丈夫です。
DIによって ViewModelへの注入も自然に行えるので、ぜひ開発に取り入れてみてください。

すだ

技術者としてのキャリアパスを次のレベルへと進めたい皆様、
未経験からIT・Webエンジニアを目指すなら【ユニゾンキャリア】
を通じて、
自分の市場価値をさらに向上させてみませんか?

それではまた次回の記事でお会いしましょう!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

弊社テックブログをご愛読いただきありがとうございます。
当テックブログを運用している株式会社メモリアインクは、
【正社員】還元率83%
【フリーランス】マージン一律5万円で案件のご紹介
と、エンジニアの皆様に分かりやすい形で稼げる仕組みを構築し提供させていただいております。

コメント

コメントする

目次