
みなさまこんにちは〜!
メモリアインクのすだです。
今回は、Hiltというライブラリを使ったDI(依存性注入)について、詳しく解説していきます!
この記事を読んでわかること…
・Hiltを使った外部ライブラリのDIについて
・@Module
、@Provides
、@InstallIn
などのHiltアノテーションについて
DI(依存性注入)やHilt(DIライブラリ)の基本については、以下の記事でそれぞれ解説しておりますので、ぜひ一緒にご覧ください。
とは?基本的な考え方と必要性についてわかりやすく解説!-300x158.png)
とは?基本的な考え方と必要性についてわかりやすく解説!-300x158.png)
のやり方をわかりやすく解説!-300x158.png)
のやり方をわかりやすく解説!-300x158.png)
環境
- 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が自動的に NetworkModule
の provideRetrofit()
を見つけて実行してくれました。
そのため、特別な指定をしなくても問題なく依存を注入することができました。
しかし、以下のように 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
インスタンスを判断し、自動で注入してくれるようになります。
まとめ
おつかれさまでした。いかがでしたでしょうか!
このあたり少々複雑で理解するのに時間がかかるかもしれませんが、
というのをまず理解するだけで大丈夫です。
DIによって ViewModelへの注入も自然に行えるので、ぜひ開発に取り入れてみてください。



技術者としてのキャリアパスを次のレベルへと進めたい皆様、
<未経験からIT・Webエンジニアを目指すなら【ユニゾンキャリア】>
自分の市場価値をさらに向上させてみませんか?
それではまた次回の記事でお会いしましょう!
コメント