11
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Jetpack Compose + MVVM + Hilt で高田健志の伝説アプリを作りたい

Last updated at Posted at 2022-12-05

はじめまして。
ひっそり未来大で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しないで書いてみます。

DensetsuViewModel.kt
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
        }
    }
}
DensetsuScreen.kt
@Composable
fun DensetsuScreen(
    ...
) {
    val repository =
        DensetsuRepository(DensetsuDatabase.getInstance(LocalContext.current).densetsuDao())

    //Factoryを呼び出して、densetsuViewModelを生成
    val densetsuViewModel: DensetsuViewModel = viewModel(
        factory = DensetsuViewModel.Factory(repository)
    )

    ...
}

4.DIして書いてみる

Hiltを使用します(公式ドキュメント)。

DensetsuViewModel.kt
@HiltViewModel
class DensetsuViewModel @Inject constructor(
    private val densetsRrepository: DensetsuRepository
    ) : ViewModel() {
    ...
}
DensetsuScreen.kt
@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を見てみます。

DensetsuRepository.kt
interface DensetsuRepository {
    ...
}

class DensetsuRepositoryImpl @Inject constructor(
    ...
) : DensetsuRepository {
    ...
}

DensetsuRepositoryはインターフェースなので、コンストラクタインジェクションができません。Hiltモジュール内に@Bindsアノテーションを付けた抽象関数を作成することで、インジェクト先にインターフェースの提供方法を教えてあげます。
@Moduleアノテーションを付けることで、Hiltモジュールになります。

DensetsuRepositoryBindModule.kt
@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さんです!
どんなお話なんでしょうか!わくわく!

11
2
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
11
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?