개발/안드로이드

[Hilt] 기존 프로젝트에 Hilt 적용해보기

스몰스테핑 2024. 7. 26. 19:02

적용할 프로젝트 폴더 계층 구조

 

https://developer.android.com/codelabs/basic-android-kotlin-compose-practice-bus-schedule-app?hl=ko&continue=https%3A%2F%2Fdeveloper.android.com%2Fcourses%2Fpathways%2Fandroid-basics-compose-unit-6-pathway-2%3Fhl%3Dko%23codelab-https%3A%2F%2Fdeveloper.android.com%2Fcodelabs%2Fbasic-android-kotlin-compose-practice-bus-schedule-app#0

 

연습: Bus Schedule 앱 빌드  |  Android Developers

Room을 사용하여 데이터 유지 Codelab에서 배운 개념을 적용하여 데이터베이스 지속성을 Bus Schedule 앱에 추가합니다.

developer.android.com

 

https://github.com/google-developer-training/basic-android-kotlin-compose-training-bus-schedule-app/tree/main

 

GitHub - google-developer-training/basic-android-kotlin-compose-training-bus-schedule-app

Contribute to google-developer-training/basic-android-kotlin-compose-training-bus-schedule-app development by creating an account on GitHub.

github.com

 

이전에 공부하며 다뤘던 연습 프로젝트인 Bus Schedule 앱에 Hilt를 적용시켜 보려고 한다.

작성했던 글을 토대로 Hilt 라이브러리를 불러오고 종속성 선언을 해주자.

 

https://small-stepping.tistory.com/1096

 

Android에서 Hilt란?

Hilt란? 프로젝트에서 종속 항목 수동 삽입을 실행하는 상용구를 줄이는 Andorid용 종속 항목 삽입 라이브러리이다. 종속 항목 삽입(DI)를 실행하려면 모든 클래스와 종속 항목을 수동으로 구성하

small-stepping.tistory.com

 

해당 프로젝트는 ksp를 쓰고 있었고 Hilt Alpha 버전을 지원하고 있지만, kapt로 바꾸어 진행하였다.

 

1. build.gradle.kts (Project)

buildscript {
    extra.apply {
        set("nav_version", "2.5.3")
        set("room_version", "2.5.1")
    }
}
plugins {
    id("com.android.application") version "8.1.1" apply false
    id("com.android.library") version "8.1.1" apply false
    id("org.jetbrains.kotlin.android") version "2.0.0" apply false //<-- Version Up to 2.0.0
    id("com.google.dagger.hilt.android") version "2.51.1" apply false // <-- Add
    id("org.jetbrains.kotlin.plugin.compose") version "2.0.0" apply false //<-- Add
    id("org.jetbrains.kotlin.kapt") version "2.0.0" apply false //<-- Add
}

 

2. build.gradle.kts (app)

plugins {
    id("com.android.application")
    id("com.google.dagger.hilt.android") //<-- Add
    id("org.jetbrains.kotlin.android")
    id("org.jetbrains.kotlin.plugin.compose") //<-- Add
    id("org.jetbrains.kotlin.kapt") //<-- Add
}

...

dependencies {

    implementation(platform("androidx.compose:compose-bom:2024.06.00"))
    implementation("androidx.activity:activity-compose:1.9.0")
    implementation("androidx.compose.material3:material3")
    implementation("androidx.compose.ui:ui")
    implementation("androidx.compose.ui:ui-graphics")
    implementation("androidx.compose.ui:ui-tooling-preview")
    implementation("androidx.core:core-ktx:1.13.1")
    implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.3")
    implementation("androidx.navigation:navigation-compose:${rootProject.extra["nav_version"]}")

    // hilt
    implementation("com.google.dagger:hilt-android:2.51.1") //<-- Add
    kapt("com.google.dagger:hilt-compiler:2.51.1") //<-- Add

    // room
    implementation("androidx.room:room-ktx:${rootProject.extra["room_version"]}")
    implementation("androidx.room:room-runtime:${rootProject.extra["room_version"]}")
    kapt("androidx.room:room-compiler:${rootProject.extra["room_version"]}") //<-- Ksp to kapt

    debugImplementation("androidx.compose.ui:ui-test-manifest")
    debugImplementation("androidx.compose.ui:ui-tooling")
}

 

 

이후 DI 되어있는 곳 Application에 @HilstAndroidApp 어노테이션을 먼저 추가하며 내부에 있던 내용물을 지운다.

import android.app.Application
import com.example.busschedule.data.AppContainer
import com.example.busschedule.data.AppDataContainer
import dagger.hilt.android.HiltAndroidApp

@HiltAndroidApp //<-- Add
class BusScheduleApplication: Application()

/* Delete
 {
    lateinit var container: AppContainer

    override fun onCreate() {
        super.onCreate()
        container = AppDataContainer(this)
    }
}
*/

 

Hilt를 사용하기 위한 것으로, 의존성 주입은 onCreate의 super.onCreate()에서 바이트 코드로 변한 뒤에 이루어진다.

내부 내용물인 AppDataContainer는 데이터베이스와 연결, ViewModel Factory에서 Repository를 사용하기 위한 코드이다.

이를 작동시키기 위해 다음과 같이 재구성하였다.

 

이제 데이터 베이스와 레포지트리의 의존성 주입을 위한 모듈을 만든다.

우선 데이터 베이스를 위해 다음과 같은 DataModule를 만든다.

import android.content.Context
import com.example.busschedule.data.BusScheduleDao
import com.example.busschedule.data.BusScheduleDatabase
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object DataModule {

    @Singleton
    @Provides
    fun provideBusScheduleDatabase(@ApplicationContext context: Context): BusScheduleDatabase = BusScheduleDatabase.getDatabase(context)

    @Singleton
    @Provides
    fun provideBusScheduleDao(database: BusScheduleDatabase): BusScheduleDao = database.busScheduleDao()
}

 

  • @InstallIn - Hilt가 생성하는 DI 컨테이너에 어떤 모듈을 사용할지 가르킨다.
  • @Module - InstallIn으로 설정한 객체들의 모음
  • @Provides - 의존성 주입
  • @ApplicationContext - applicationContext를 제공

 

 

  1. @HiltAndroidApp을 통해 ApplicationComponent가 생성된다.
  2. @InstallIn을 통해 해당 컴포넌트의 모듈이 생성된다.
  3. Activity에 @AndroidEntryPoint를 통해 ActivityComponent가 생성된다.
  4. Repository를 주입받는다.

 

데이터 모듈에서 SingletonComponent로 생성했기에 @Singleton을 설정해줘야 한다.

Repository의 구현부에 다음과 같이 @Singleton, @Inject constructor를 추가한다.

package com.example.busschedule.data

import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class OfflineBusScheduleRepository @Inject constructor(private val busScheduleDao: BusScheduleDao): BusScheduleRepository {
    ...
}

 

ViewModel에는 @HiltViewModel, @Inject constructor를 추가한다.

Hilt의 의존성 주입은 @Inject를 통해 주입이 가능하기 때문이다.

import androidx.lifecycle.ViewModel
import com.example.busschedule.data.BusSchedule
import com.example.busschedule.data.BusScheduleRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject

@HiltViewModel
class BusScheduleViewModel @Inject constructor(private val repository: BusScheduleRepository): ViewModel() {

    // Get example bus schedule
    fun getFullSchedule(): Flow<List<BusSchedule>> = repository.getAllSchedule()

    // Get example bus schedule by stop
    fun getScheduleFor(stopName: String): Flow<List<BusSchedule>> = repository.getStopByName(stopName)
}

 

이후 다음과 같은 RepositoryModule를 만든다.

import com.example.busschedule.data.BusScheduleRepository
import com.example.busschedule.data.OfflineBusScheduleRepository
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ViewModelComponent

@Module
@InstallIn(ViewModelComponent::class)
interface RepositoryModule {
    @Binds
    fun bindRepository(repository: OfflineBusScheduleRepository): BusScheduleRepository
}

 

 

위에서 다 작업을 했다면 이제 Activity에서 사용할 수 있으므로 MainActivity에 @AndroidEntryPoint를 추가한다.

(만약 ContentProvider, DFM, Dagger를 사용하지 않는 서드파티 라이브러리들은 @EntryPoint를 사용한다.)

 

MainActivity의 하위 Fragment가 있다면 마찬가지로 @AndroidEntryPoint를 추가해줘야한다. 외에도 Service, BroadcastReceiver도 사용 가능하다.

JetpackCompose의 요소 fun ~~에 추가하면 상위 수준에 추가하라고 에러가 발생한다.

 

생성자 외에도 변수에 @Inject를 할 수 있으나 lateinit으로 설정해야하며 private도 사용할 수 없다.

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import com.example.busschedule.ui.BusScheduleApp
import com.example.busschedule.ui.theme.BusScheduleTheme
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            BusScheduleTheme {
                BusScheduleApp()
            }
        }
    }
}

 

https://developer.android.com/codelabs/android-hilt?hl=ko#0

 

Android 앱에서 Hilt 사용  |  Android Developers

이 Codelab에서는 Hilt를 사용하여 종속 항목 삽입을 실행하는 Android 앱을 빌드해 보겠습니다.

developer.android.com

https://readystory.tistory.com/210

 

[Android] Room + Hilt 사용할 때 초기 데이터 설정하기(pre-populate)

Room 을 사용하다보면 초기 데이터를 넣어야 하는 경우가 있을 겁니다. Developer 사이트에 의하면 데이터베이스에 미리 값을 채우는 방식으로 assets 과 File system 을 사용하는 방법을 제시하고 있는

readystory.tistory.com

https://stackoverflow.com/questions/64820394/using-dagger-hilt-in-repository-class

 

Using dagger-hilt in repository class

Please consider the following class: class MainRepository constructor( private val blogDao: BlogDao, private val blogRetrofit: BlogRetrofit, private val cacheMapper: CacheMapper, pr...

stackoverflow.com

https://stackoverflow.com/questions/65966105/dagger-hilt-cannot-be-provided-without-an-provides-annotated-method

 

Dagger Hilt: cannot be provided without an @Provides-annotated method

When I use interface like below in viewmodel class MainViewModel @ViewModelInject constructor( private val trafficImagesRepository: TrafficImageRepository, <----------------- Not working @

stackoverflow.com

https://moonbari.tistory.com/99

 

[Android] Hilt란 무엇일까

1. 개요 프로젝트를 할때마다 편하게 쓰는 Hilt를 더 배우고 싶어 DI부터 다시 정리할려고 합니다. 본 포스트는 드로이드나이츠 2020, 옥수환 님의 Hilt 강의를 보며 작성하였습니다. 2. 본문 2-1. DI란

moonbari.tistory.com

https://it-of-fortune.tistory.com/26

 

안드로이드 힐트(hilt) 의존성 주입 사용

안녕하세요. 이번 포스팅에서는 hilt를 사용한 dependency injection을 간단하게 구현해 보겠습니다. 언어: 코틀린 sdk vsersion - compile: 33 - min: 21 - target: 33 저는 아래의 포스팅에서 사용한 소스에 적용해

it-of-fortune.tistory.com

https://velog.io/@201/Hilt%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-MVVM-%EC%95%84%ED%82%A4%ED%85%8D%EC%B3%90-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0

 

Hilt를 사용하여 안드로이드 MVVM 아키텍쳐 구현하기

이번 글에서는 Hilt를 이용하여 MVVM아키텍쳐 구조의 간단한 노트를 구현해보고자 합니다. 혹시 MVVM패턴에 익숙하지 않다면 아래 글에 자세히 설명되어 있습니다https://velog.io/@201/mvvmarchitecture안드

velog.io