20210123のAndroidに関する記事は4件です。

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/836902cdd322f9accded

Hiltのインストール

ルート直下の「build.gradle」にプラグインを追加します。

/biuld.gradle
buildscript {
    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"
}

以下はなくても動作しましたが、公式ドキュメントに記載されているので追加しています。
詳しい方がいたら教えていただけると嬉しいです :pray:

/app/build.gradle
android {
+     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.kt
interface MonstersRepository {
    // ...
}
MonstersFirestoreClient.kt
class MonstersFirestoreClient : MonstersRepository {
    // ...
}

上記のインターフェースを通して、以下のビューモデルにDIする方法を紹介します。
現状ではビューモデル内で実装クラスのインスタンスを生成しているため、DIできていません。

MonsterListViewModel.kt
class 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.kt
package 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.kt
class MonsterListFragment : Fragment() {
    private val viewModel: MonsterListViewModel // TODO: DIする
}
MonsterListViewModel.kt
import 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日目の記事でした。

参考リンク

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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.gradle
apply plugin: 'kotlin-kapt'

これで無事BRクラスに監視変数が作成されました!!

最後に

Java ではこのような記述が必要ではないため、Kotlin の場合でも DataBinding の導入では以下の記述だけ追加すれば良いと考えておりました。

app/build.gradle
dataBinding {
    enabled = true
}

// もしくは
buildFeatures {
    dataBinding = true
}

解決までに数時間費やしてしまい、今回また勉強になりました。

参考リンク

ありがとうございます!
参考にさせていただきました!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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しておいた

https://github.com/ikemura23/Android-Kotlin-Lab/blob/master/app/src/main/java/com/ikemura/android_kotlin_lab/dialog/dialogfragment/TransportDialogFragment2.kt

参考リンク

material-components-android/getting-started.md at master
https://github.com/material-components/material-components-android/blob/master/docs/getting-started.md

Dialogs - Material Design
https://material.io/components/dialogs

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Gradleプラグインを適用するスマートな書き方

app/build.gradle

before
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
after
plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-android-extensions'
}

参考リンク

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む