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

【Android】<Kotlin>ViewModelに依存を渡す方法(DI)|FactoryとHiltを丁寧に比較解説

【Android】<Kotlin>ViewModelに依存を渡す方法(DI)|FactoryとHiltを丁寧に比較解説
すだ

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

今回はViewModelへのDIにおいて、FactoryまたはHilt(DIライブラリ)を使うやり方をわかりやすく解説していきます。

この記事を読んでわかること…
・ViewModelにおけるFactoryとは?
・Hiltを使ってViewModelにDI(依存性注入)する方法

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

目次

環境

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

ViewModelに依存性を注入したい理由とは

Androidアプリ開発で ViewModel は、UIに表示するデータの管理役です。
しかし、以下のような処理をViewModelが直接やるのは望ましくありません。

  • ネットワーク通信(API呼び出し)
  • データベースアクセス
  • 設定やログの処理

こういった処理は Repository などの別のクラスに分けて、
ViewModelには「UIロジックだけを任せる」ようにするのが理想です。

そのために必要なのが、「依存性注入(DI)」です。

ViewModelにおける依存性注入の方法2つ解説

ViewModelに依存(例:Repository)を注入する方法は大きく分けて 2通り あります。

① Factoryを使った手動DI(自作のFactoryで依存を渡す)
② Hiltを使う(DIライブラリに依存の生成と注入を任せる)

それぞれ順番に説明していきます。

ViewModelFactory を使った手動DIの書き方

プログラミングにおける Factory(ファクトリー) とは、オブジェクトの生成方法(作り方)を定義するクラスや関数のことです。

特定のクラスを生成するために必要な処理や引数をひとまとめにしておくことで、
呼び出し側は その作り方を意識することなく、必要なインスタンスを取得できるようになります。

引数なしの ViewModel の場合

class UserViewModel : ViewModel() {
    fun getUserName(): String = "山田太郎"
}

このような引数のない ViewModel であれば、以下のように ViewModelProvider を使ってインスタンスを取得できます。

class MainActivity : AppCompatActivity() {

    private lateinit var viewModel: UserViewModel

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

        viewModel = ViewModelProvider(this)[UserViewModel::class.java]

        println(viewModel.getUserName())
    }
}

引数ありの ViewModel の場合

class UserViewModel(private val repository: UserRepository) : ViewModel() {
    fun getUserName(): String = repository.fetchUser()
}

一方 このように引数があるViewModel の場合、ViewModelProvider(this) では直接インスタンスを生成できません。
なぜならViewModelProvider は デフォルトでは引数なしのコンストラクタしか扱えないためです。

そのため、引数ありの ViewModel を使う場合は、
専用の Factory クラスを作成し、それを ViewModelProvider に渡す必要があります。

class UserViewModelFactory(
    private val repository: UserRepository
) : ViewModelProvider.Factory {
    
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return UserViewModel(repository) as T
    }
}

create() は、ViewModelProvider によって自動的に呼ばれる関数です。
modelClass には、呼び出し元で指定された ViewModel のクラス型が渡されます(この例では UserViewModel::class.java)。

as Tは、「UserViewModel は T 型(=ViewModelのサブクラス)として返します」と明示している、型キャストの記述

→つまりは、この関数の中で UserViewModel(repository) を生成して返しています。

そして呼び出し側で以下のように書きます。

class MainActivity : AppCompatActivity() {

    private lateinit var viewModel: UserViewModel

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

        val factory = UserViewModelFactory(UserRepository())
        viewModel = ViewModelProvider(this, factory).get(UserViewModel::class.java)

        println(viewModel.getUserName())
    }
}

UserViewModelFactory のインスタンスを生成して、ViewModelProvider の第二引数に渡すことで、
ViewModelProvider は その Factory の中で定義された処理に従って ViewModel を生成します。

つまり、ViewModelProviderViewModelの引数を自動的に把握しているわけではなく、
「Factoryがどうやって作るか」を実行するだけとなります。

これがFactoryを使ったViewModelの手動DIです。

Hiltを使った自動DIの書き方

一方、Hilt(DIライブラリ)を使用すると、Hiltが内部でFactoryの機能を実行してくれます。
そのため、Factoryクラスを作ることなく自動でDIの仕組みを実現することができます。

Hiltの基本的な知識については、以下の記事にまとめておりますのでぜひご覧ください。

まずは、Repositoryクラスのコンストラクタに@injectを付与します。▼

class UserRepository @Inject constructor() {
    fun fetchUser(): String = "Hilt太郎"
}

そして、ViewModelクラスのコンストラクタにも@injectを付与しつつ、
クラス名上に@HiltViewModelを付与します。▼

@HiltViewModel
class UserViewModel @Inject constructor(
    private val repository: UserRepository
) : ViewModel() {
    fun getUserName(): String = repository.fetchUser()
}

@HiltViewModel は「このクラスはHiltに管理されるViewModelです」とHiltに教えるためのアノテーションです。
これを使うことで、HiltはそのViewModelに依存関係(例:Repositoryなど)を自動で注入できるようになります。

最後に、呼び出し側のコンポーネントにアノテーションをつけて(ここではActivityなので@AndroidEntryPoint)、
by viewModelsを使ってViewModelのインスタンスを取得します。

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    private val viewModel: UserViewModel by viewModels()

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

        println(viewModel.getUserName())
    }
}

by viewModels() は、Kotlinの委譲プロパティを使った構文です。
これは Android Jetpack が提供する拡張関数で、内部的には ViewModelProvider を使ってViewModelを取得しています。

Hiltを使っている場合は、ViewModelProvider(this).get(...) ではなく、
by viewModels() を使うことで Hilt の ViewModelFactory が正しく使われ、依存性注入が機能します。

ViewModelProvider(this).get(...)の書き方だと、Hilt専用の ViewModelFactory(HiltViewModelFactory)が使われない。そのためHiltが提供する依存注入が実行されず、エラーや未初期化の状態になる)

まとめ

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

ViewModelに、Repositoryなどの依存オブジェクトを渡す設計は、よくあるパターンです。
毎回Factoryクラスを作成するのが負担に感じる場合は、Hiltの導入をぜひ検討してみてください。

すだ

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

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

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

この記事を書いた人

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

コメント

コメントする

目次