🐥

Jetpack ComposeでHiltを使ってみた!

2023/11/29に公開

Overview

Hiltとは公式によると...

Hilt は、Android アプリで依存性を注入するための推奨されるソリューションであり、Compose とシームレスに連携します。

ViewModel セクションに記載されている viewModel() 関数は、Hilt が @HiltViewModel アノテーションを使用して構築する ViewModel を自動的に使用します。Hilt の ViewModel 統合に関するドキュメントが提供されています。

https://developer.android.com/jetpack/compose/libraries?hl=ja#hilt

🧐依存性を注入するとは何か?

分かりやすく言うと、上書きすることです。今回はダミーのデータをListに入れて、それを表示するだけです。これパッケージを使ってやっています。

summary

まずは、パッケージをbuild.gradleに追加しましょう!
https://developer.android.com/training/dependency-injection/hilt-jetpack?hl=ja#viewmodel-navigation

Hiltを追加
plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
}

android {
    namespace 'com.junichi.hilttutorial'
    compileSdk 33

    defaultConfig {
        applicationId "com.junichi.hilttutorial"
        minSdk 24
        targetSdk 33
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        vectorDrawables {
            useSupportLibrary true
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
    buildFeatures {
        compose true
    }
    composeOptions {
        kotlinCompilerExtensionVersion '1.3.2'
    }
    packagingOptions {
        resources {
            excludes += '/META-INF/{AL2.0,LGPL2.1}'
        }
    }
}

dependencies {

    implementation 'androidx.core:core-ktx:1.8.0'
    implementation platform('org.jetbrains.kotlin:kotlin-bom:1.8.0')
    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
    implementation 'androidx.activity:activity-compose:1.5.1'
    implementation platform('androidx.compose:compose-bom:2022.10.00')
    implementation 'androidx.compose.ui:ui'
    implementation 'androidx.compose.ui:ui-graphics'
    implementation 'androidx.compose.ui:ui-tooling-preview'
    implementation 'androidx.compose.material3:material3'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
    androidTestImplementation platform('androidx.compose:compose-bom:2022.10.00')
    androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
    debugImplementation 'androidx.compose.ui:ui-tooling'
    debugImplementation 'androidx.compose.ui:ui-test-manifest'

    // Hiltの依存関係を追加
    implementation 'androidx.hilt:hilt-navigation-fragment:1.0.0'
}

📁ディレクトリ構成

MVVMらしいコードになっていたので、ファイルを分けました。このようになっております。

それぞれのファイルの解説をしていきます。

データを扱うdata class。こちらにダミーデータで使うデータ型のメンバー変数を定義します。

モデル
package com.junichi.hilttutorial.model

data class User(
    val id: Long,
    val name: String,
    val email: String
)

依存性の注入(DI)して、データの永続化とまでは、いかないけどダミーのデータを入れるシングルトンのクラスを作ります。シングルトンだから、同じインスタンを生成しないんでしょうね。この辺がいまだにわからない?
コンストラクタを使うので、初期値というか、インスタンスが呼び出されたら、最初に実行される処理があって、その中に、DIする関数がありましてこれが実行されて、View側に表示するダミーのデータを入れます。
一応初期値ですね...

DI
package com.junichi.hilttutorial.repository

import com.junichi.hilttutorial.model.User
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class UserRepository @Inject constructor() {
    // getUserが依存しているgetUsersを、UserRepositoryのコンストラクタで注入する
    fun getUser(id: Long): User? {
        return getUsers().find { it.id == id }
    }
    // getUserを使用して、依存性の注入をする
    fun getUsers(): List<User> {
        return listOf(
            User(id = 123, name = "James Bond", "jamesbond@007.com"),
            User(id = 345, name = "Batman", "batman@cave.com"),
            User(id = 999, name = "Arya Stark", "arya@winterfell.com")
        )
    }
}

View側に、データを保存するよと通知をする必要があります。MVVMの設計だと、VM(View Model)をここで、使います。レポジトリのロジックを呼び出して状態を渡しているだけ。

ViewModel
package com.junichi.hilttutorial.viewModel

import androidx.lifecycle.ViewModel
import com.junichi.hilttutorial.model.User
import com.junichi.hilttutorial.repository.UserRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import javax.inject.Inject

@HiltViewModel
class HomeScreenViewModel @Inject constructor(val userRepository: UserRepository) : ViewModel() {
    private val _users = MutableStateFlow(userRepository.getUsers())
    val users: StateFlow<List<User>> = _users
}

ダミーデータを表示するComposable関数であるHomeScreenを作成します。

アプリのUI
package com.junichi.hilttutorial

import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.junichi.hilttutorial.repository.UserRepository
import com.junichi.hilttutorial.viewModel.HomeScreenViewModel

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun HomeScreen() {
    // インスタンを返す関数を呼び出す
    val vm: HomeScreenViewModel = hiltViewModel()

    // インスタンを返す関数を代入した変数を使用する
    val users by vm.users.collectAsState()

    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text(text = "Hiltを使ってみた?") }
            )
        }
    // LazyColumnで、依存性の注入をしたダミーのユーザーデータを表示する
    ) { paddingValues ->
        LazyColumn(modifier = Modifier.padding(paddingValues)) {
            items(users) { user ->
                Text(text = "ID: ${user.id}")
                Spacer(modifier = Modifier.padding(8.dp))
                Text(text = "お名前: ${user.name}")
                Spacer(modifier = Modifier.padding(8.dp))
                Text(text = "メールアドレス: ${user.email}")
                Spacer(modifier = Modifier.padding(8.dp))
            }
        }
    }
}

// この関数は、HomeScreenViewModelのインスタンスを返す
fun hiltViewModel(): HomeScreenViewModel {
    return HomeScreenViewModel(UserRepository())
}

MainActivity.ktでHomeScreenをimportして、ビルドしたときにエミュレーターに表示できるようにします。

アプリを実行するコード
package com.junichi.hilttutorial

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.junichi.hilttutorial.ui.theme.HiltTutorialTheme

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            HiltTutorialTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    HomeScreen()
                }
            }
        }
    }
}


@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
    HiltTutorialTheme {
        HomeScreen()
    }
}

ビルドに成功するとこのような画面が表示されます。

thoughts

今回は、Androidのネイティブアプリで、依存性の注入なるものをやってみました。できているかは疑問ですが😅
なんでこれをやろうかなと思ったかというと、API通信をするチュートリアルをやるときに、Hiltも使って機能実装をしていたので、学習する必要が出てきたのでやってみました。

今回参考にしたMediumの記事
このままやっても動きません!
https://blog.canopas.com/jetpack-compose-with-dagger-hilt-mvvm-and-navcontroller-b6048bb85073

こちらがGithubのサンプルコードです
全体のコードが見れます。
https://github.com/sakurakotubaki/HiltTutorial

Discussion