はじめまして。
ひっそり未来大でM1やってます。ゆめと申します(Twitter, Instagram)。
本記事は、FUN Advent Calendar 2022 part2の5日目の記事です。
アドカレに便乗して、初めての技術ブログを書きたいなと思い、参加させていただきました。最近触った技術の備忘録を書こうと思います()。
ブログを書くの初めて+Androidよわよわの人なので、不備や間違い等がありましたらご指摘いただけると喜びます。
1.はじめに
筆者は最近Jetpack Composeの勉強を始めました。
Composeで〜、MVVMで〜、API叩いて〜、それをRoomに保存して〜、なアプリを作ろうと手を動かしていると、「composeの場合、引数があるViewModelはどう実装するのだろう」と、悩んでしまいました。
色々なサイトを参照したのですが、みんなDIを使っている(DIとは)。
たしかにDI大事だよね。最近流行ってるし。
.
.
.
DIの学習コストが高い!!!!!!!!
- ただAPIを叩きたいだけなのに…
- DIしないとviewModelってviewで使えないの?
- DIのこと調べてたら、一生コードが進まない…
- DIナシで書く->導入のリファクタを通じて、DIの恩恵とか、導入方法とか、そういうのを学びたいよ…
ということで、依存性を注入しないで書く->DIして書き直す流れをまとめようと思います。
2.作ったもの
とりあえず作ったものが、こちらになります。
https://github.com/ym223/takadaKenshiDensetsuAndroid
実装済みの機能は以下になります。
- 伝説を探すボタンをタップ->ランダムに1つ表示
- リスト画面で一覧表示
本記事では、高田健志の伝説APIを使用します。
3.DIしないで書く
早速ですが、DIしないで書いてみます。
class DensetsuViewModel(private val densetsuRepository: DensetsuRepository) : ViewModel() {
...
//DensetsuViewModelクラスのインスタンスを返すためのFactory
//ここでViewModelに渡す引数を定義する
class Factory(private val densetsuRepository: DensetsuRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
return DensetsuViewModel(this.densetsuRepository) as T
}
}
}
@Composable
fun DensetsuScreen(
...
) {
val repository =
DensetsuRepository(DensetsuDatabase.getInstance(LocalContext.current).densetsuDao())
//Factoryを呼び出して、densetsuViewModelを生成
val densetsuViewModel: DensetsuViewModel = viewModel(
factory = DensetsuViewModel.Factory(repository)
)
...
}
4.DIして書いてみる
Hiltを使用します(公式ドキュメント)。
@HiltViewModel
class DensetsuViewModel @Inject constructor(
private val densetsRrepository: DensetsuRepository
) : ViewModel() {
...
}
@Composable
fun DensetsuScreen(
...
densetsuViewModel: DensetsuViewModel = hiltViewModel()
) {
...
}
なんだかすごくスッキリしました!!!
DensetsuViewModel.kt
では、Factoryで引数の定義をしていないし、
DensetsuScreen.kt
では引数にdensetsuViewModel
を渡してあげているだけです!
5.何が起きているのか
DIする前はDensetsuScreen.kt
で、DensetsuRepository
インスタンスを生成するために、具体的な値を渡していました。
//DensetsuScreen.kt
@Composable
fun DensetsuScreen(
...
+ densetsuViewModel: DensetsuViewModel = hiltViewModel()
) {
- val repository =
- DensetsuRepository(DensetsuDatabase.getInstance(LocalContext.current).densetsuDao())
- //Factoryを呼び出して、densetsuViewModelを生成
- val densetsuViewModel: DensetsuViewModel = viewModel(
- factory = DensetsuViewModel.Factory(repository)
- )
...
}
DIした後では、DensetsuViewModel.kt
で、コンストラクタにDensetsuRepository
インスタンスを渡しています。
//DensetsuViewModel.kt
+ @HiltViewModel
- class DensetsuViewModel(private val densetsuRepository: DensetsuRepository): ViewModel() {
+ class DensetsuViewModel @Inject constructor(
+ private val densetsuRepository: DensetsuRepository
+ ): ViewModel() {
...
- //DensetsuViewModelクラスのインスタンスを返すためのFactory
- //ここでViewModelに渡す引数を定義する
- class Factory(private val densetsuRepository: DensetsuRepository) : ViewModelProvider.Factory {
- override fun <T : ViewModel> create(modelClass: Class<T>): T {
- @Suppress("UNCHECKED_CAST")
- return DensetsuViewModel(this.densetsuRepository) as T
- }
}
}
DI前のように具体的な値を渡していないため、これではDensetsuRepository
インスタンスの作り方がわからないような気がします。
ここで、DensetsuRepository.kt
を見てみます。
interface DensetsuRepository {
...
}
class DensetsuRepositoryImpl @Inject constructor(
...
) : DensetsuRepository {
...
}
DensetsuRepository
はインターフェースなので、コンストラクタインジェクションができません。Hiltモジュール内に@Binds
アノテーションを付けた抽象関数を作成することで、インジェクト先にインターフェースの提供方法を教えてあげます。
@Module
アノテーションを付けることで、Hiltモジュールになります。
@Module
@InstallIn(SingletonComponent::class)
abstract class DensetsuRepositoryBindModule {
@Singleton
@Binds
abstract fun densetsuRepository(densetsuRepositoryImpl: DensetsuRepositoryImpl): DensetsuRepository
}
これで、DensetsuRepository
インターフェースを使うときに、denstsuRepositoryImpl
クラスのインスタンスを渡すということをHiltに教えてあげます。
こんな感じで、具体的な値を渡さなくてもDensetsuViewModel
インスタンスを生成することができるようです!
6.DIを勉強してみて
規模が小さいアプリなので、正直あまりDIの必要性や恩恵は受けられなかった感はあります。
大規模なチーム開発になったときに、単体テストのしやすさや、機能ごとの開発のしやすさなど、もっとちゃんと恩恵を実感できるのかなという感じがしました。
また、コード書いているときはよくわからないまま書いていたりもしたのですが、今この記事を書きながら、めちゃめちゃ理解が深まった気がします。ちょっとだけ。
とにかく、実績解除:DI触ったことある!!!
まだまだ理解しきれないことも多いので、触りながら学んでいこうと思います。
参考
Android Developers:Hilt を使用した依存関係の注入
DI(依存性の注入)とは依存性を注入するということである、、?
Android - Jetpack Compose - Room - Retrofit - MVVM - Hilt - Pruebas unitarias - REST API - SQLite
最後に
考えながら書いてたら、めちゃ重な記事になってしまいました。
今日はもう終わりかけです。大遅刻です。すみません。
明日(12/6)は、Yama-yeahさんと、moriさんです!
どんなお話なんでしょうか!わくわく!