- 投稿日:2021-01-23T19:11:11+09:00
DIライブラリ「Hilt」のセットアップ&使い方(Kotlin)
はじめに
本記事は Swift/Kotlin愛好会 Advent Calendar 2020 の21日目の記事です。
空いているので埋めました。Hiltのセットアップ方法と使い方を紹介します。
「Hilt」とは?
Android向けのDIライブラリです。
Daggerの上に構築されており、DaggerコンポーネントやAndroidクラスを自動的に注入するコードを生成するため、Daggerを使うよりボイラープレートが減ります。
環境
- OS:macOS Big Sur 11.1
- Android Studio:4.1.2
- Kotlin:1.4.10
- Gradle:6.8
- Gradle plugin:4.1.2
- Hilt:2.31-alpha
セットアップ
kaptのセットアップ
Hiltはアノテーションを使ってコードを生成するため、kaptが必要です。
以下の記事を参考にセットアップしてください。
https://qiita.com/uhooi/items/836902cdd322f9accdedHiltのインストール
ルート直下の「build.gradle」にプラグインを追加します。
/biuld.gradlebuildscript { repositories { google() jcenter() } dependencies { + classpath 'com.google.dagger:hilt-android-gradle-plugin:2.31-alpha' } }appフォルダ配下の「build.gradle」にプラグインの適用と依存関係を追加します。
/app/build.gradle+ apply plugin: 'dagger.hilt.android.plugin' dependencies { + // Hilt + def hilt_version = '2.31-alpha' + implementation "com.google.dagger:hilt-android:$hilt_version" + kapt "com.google.dagger:hilt-android-compiler:$hilt_version" + def hilt_viewmodel_version = '1.0.0-alpha02' + implementation "androidx.hilt:hilt-lifecycle-viewmodel:$hilt_viewmodel_version" + kapt "androidx.hilt:hilt-compiler:$hilt_viewmodel_version" }以下はなくても動作しましたが、公式ドキュメントに記載されているので追加しています。
詳しい方がいたら教えていただけると嬉しいです
/app/build.gradleandroid { + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } } + kapt { + correctErrorTypes = true + }アプリケーションクラスの実装
アプリケーションクラスに
@HiltAndroidApp
アノテーションを付けます。
これにより、アプリケーションレベルのコンポーネントがHiltで使えるようになります。要は「このアプリではHiltを使いますよー」というおまじないです。
UhooiPicBookApp.kt+ import dagger.hilt.android.HiltAndroidApp + @HiltAndroidApp class UhooiPicBookApp : Application() { // ... }これでHiltのセットアップは完了です。
使い方
①インターフェースを通してコンストラクタインジェクションする
Before
例として、以下のインターフェースとその実装クラスを用意します。
MonstersRepository.ktinterface MonstersRepository { // ... }MonstersFirestoreClient.ktclass MonstersFirestoreClient : MonstersRepository { // ... }上記のインターフェースを通して、以下のビューモデルにDIする方法を紹介します。
現状ではビューモデル内で実装クラスのインスタンスを生成しているため、DIできていません。MonsterListViewModel.ktclass MonsterListViewModel : ViewModel() { private val repository: MonstersRepository = MonstersFirestoreClient() // TODO: DIする }After
ビューモデルのコンストラクタに
@Inject
アノテーションを付け、コンストラクタでインターフェースを型とした引数を用意します。
これにより、Hiltが自動的に対象インターフェースの実装クラスをDIします。MonsterListViewModel.kt+ import javax.inject.Inject - class MonsterListViewModel : ViewModel() { + class MonsterListViewModel @Inject constructor( + private val repository: MonstersRepository + ) : ViewModel() { - private val repository: MonstersRepository = MonstersFirestoreClient() // TODO: DIする }実装クラスのコンストラクタにも
@Inject
アノテーションを付ける必要があります。MonstersFirestoreClient.kt+ import javax.inject.Inject - class MonstersFirestoreClient : MonstersRepository { + class MonstersFirestoreClient @Inject constructor() : MonstersRepository { // ... }しかし、インターフェースの実装クラスは複数あるかもしれないので、これだけではHiltがどの実装クラスをDIしていいかわかりません。
そこで用意するのがHiltモジュールです。
Hiltモジュール内で「このインターフェースにはこの実装クラスをDIする」ことを定義し、Hiltにインスタンスの提供方法を伝えます。Hiltモジュールを新規作成します。
AppModule.ktpackage com.theuhooi.uhooipicbook.di import com.theuhooi.uhooipicbook.modules.monsterlist.MonstersRepository import com.theuhooi.uhooipicbook.repository.monsters.firebase.MonstersFirestoreClient import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) abstract class AppModule { @Singleton @Binds abstract fun bindMonstersRepository(monstersFirestoreClient: MonstersFirestoreClient): MonstersRepository }コードを上から説明します。
Hiltモジュールであることを表すために
@Module
アノテーションを付けます。Hiltモジュールの依存関係をどこまで適用するかの範囲を表すため、
@InstallIn
アノテーションを付けます。
SingletonComponent
は、Hiltモジュールの全依存関係がアプリの全アクティビティで使えることを表します。
アプリ全体で適用されるHiltモジュールは、ルート直下にdi
パッケージを作成し、その中にAppModule.kt
という名前で実装するのが一般的なようです。インターフェースにDIする場合は、Hiltモジュールを抽象クラスで定義します。
インスタンスの提供方法を伝える関数も抽象関数とし、@Binds
アノテーションを付けます。
すべての依存関係で1つのインスタンスを使い回すには@Singleton
アノテーションを付けます。関数は
bind{インターフェース名}({実装クラス名の先頭を小文字にしたもの}: {実装クラス名}): {インターフェース名}
と定義するのが一般的なようです。
試していないですが、インターフェースと実装クラスさえ指定していれば、関数名と引数名は任意だと思います。これで「
MonstersRepository
インターフェースにMonstersFirestoreClient
クラスのインスタンスをDIする」ことをHiltに伝えられたので、DIに必要な実装が完了です。②自分で所有していないクラスをDIする
まだ実装したことがないため、簡単にのみ説明します。
インターフェースのときとは異なり、Hiltモジュールを
object
で定義し、 インスタンスの提供方法を伝える関数は通常の関数に@Provides
アノテーションを付けます。
関数内でインスタンスを生成して返します。AppModule.kt@Module @InstallIn(SingletonComponent::class) object AppModule { @Probides fun provideGoogleRepository(): GoogleRepository { return GoogleDBClient() }③ビューモデルをフィールドインジェクションする
Before
例として、以下のフラグメントに先ほど紹介した
MonsterListViewModel
をフィールドインジェクションする方法を紹介します。MonsterListFragment.ktclass MonsterListFragment : Fragment() { private val viewModel: MonsterListViewModel // TODO: DIする }MonsterListViewModel.ktimport javax.inject.Inject class MonsterListViewModel @Inject constructor( private val repository: MonstersRepository ) : ViewModel() { // ... }After
ビューモデルに
@HiltViewModel
アノテーションを付けます。MonsterListViewModel.kt+ import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject + @HiltViewModel class MonsterListViewModel @Inject constructor( private val repository: MonstersRepository ) : ViewModel() { // ... }フラグメントに
@AndroidEntryPoint
アノテーションを付け、ビューモデルの変数にby viewModels()
を付ければ、自動的にDIされます。MonsterListFragment.kt+ import androidx.fragment.app.viewModels + import dagger.hilt.android.AndroidEntryPoint + @AndroidEntryPoint class MonsterListFragment : Fragment() { - private val viewModel: MonsterListViewModel // TODO: DIする + private val viewModel: MonsterListViewModel by viewModels() }
@AndroidEntryPoint
アノテーションは依存するクラスにも付ける必要があります。
例えば、フラグメントをにアノテーションを付ける場合は、そのフラグメントを使っているアクティビティ(今回はMainActivity
とする)にもアノテーションを付ける必要があるということです。MainActivity.kt+ import dagger.hilt.android.AndroidEntryPoint + @AndroidEntryPoint class MainActivity : AppCompatActivity() { // ... }
viewModels()
は対象のクラス(今回はMonsterListFragment
)のみで生存するようです。
MonsterListFragment
を使用しているアクティビティ(MainActivity
)でも生存したい場合はactivityViewModels()
とすればいいようです。おわりに
少ないコード量でDIを実現できました!
Hiltにはできることがまだまだたくさんあるので、必要に応じて勉強していきたいです。以上、 Swift/Kotlin愛好会 Advent Calendar 2020 の21日目の記事でした。
参考リンク
- 投稿日:2021-01-23T13:49:14+09:00
【Android / Kotlin】プロジェクトでアノテーション(@)を使えるようにする
はじめに
Kotlin でデータバインディングを使うために、kapt の導入が必要であることを知った。
具体的に言うと自分の場合は
@get:Bindable ~
と言う記述をしてもBRクラス
に監視変数が生成されずにつまずきました。調べた結果 kapt の導入に行き着きました。なぜ必要なのか
kapt とは kotlin-annotation-processing-tools の略であり、Kotlin プロジェクトでもアノテーション(@)を使い自動コード生成ができるようにするもの。
つまり、Dagger や DataBinding などを利用する際に導入が必要となるようである。環境
- Android Studio: 4.1.1
- Build #AI-201.8743.12.41.6953283, built on November 5, 2020
- Runtime version: 1.8.0_242-release-1644-b3-6915495 x86_64
- VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o
- macOS: 10.15.7
導入
私の場合はこの1行追加するだけで使用できました。
app/build.gradle// 一部抜粋 plugins { id 'com.android.application' id 'kotlin-android' id 'kotlin-kapt' // ←ここに追加 }上記ではなく、こちらの1行の追加でも大丈夫なはず
app/build.gradleapply plugin: 'kotlin-kapt'これで無事
BRクラス
に監視変数が作成されました!!最後に
Java ではこのような記述が必要ではないため、Kotlin の場合でも DataBinding の導入では以下の記述だけ追加すれば良いと考えておりました。
app/build.gradledataBinding { enabled = true } // もしくは buildFeatures { dataBinding = true }解決までに数時間費やしてしまい、今回また勉強になりました。
参考リンク
ありがとうございます!
参考にさせていただきました!
- 投稿日:2021-01-23T13:48:58+09:00
DialogFragmentの背景色を透明 or 指定する
やりたい事
マテリアルデザイン化したDialogFragmentの背景のグレー(スクリム)を透明 or 色を指定したい
スクリムとは何か?はMaterial Designガイドラインで説明されている
https://material.io/components/dialogs#anatomy完成形
実装コード
やり方は style.xml or コードの2通りがある。
style パターン
style.xml<!-- https://material.io/components/dialogs/android#dialogs --> <style name="TransportDialog" parent="ThemeOverlay.MaterialComponents.MaterialAlertDialog"> <!-- 透明にする --> <item name="android:backgroundDimEnabled">false</item> <!-- 色を調整する --> <item name="backgroundDimAmount">10f</item> </style>このstyleをDialogFragmentにセットする
class TransportDialogFragment : DialogFragment(R.layout.transparent_dialog_fragment) { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { // スタイルをセット setStyle(STYLE_NORMAL, R.style.TransportDialog) return super.onCreateDialog(savedInstanceState) }これで色が変更できた。
コード パターン
class TransportDialogFragment : DialogFragment(R.layout.transparent_dialog_fragment) { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { return super.onCreateDialog(savedInstanceState).apply { // Dialog背景色を指定 window?.setDimAmount(0f) // 0だと透明になるので適宜調整する } } }これでも色が変更できる。
全体コード
GitHubにUPしておいた
参考リンク
material-components-android/getting-started.md at master
https://github.com/material-components/material-components-android/blob/master/docs/getting-started.mdDialogs - Material Design
https://material.io/components/dialogs