[안드로이드 Hilt 1] - Constructor, Field, Method Injection

이영준·2022년 11월 24일
1

안드로이드_Hilt

목록 보기
1/5

의존성 주입은 사실 프레임워크도 라이브러리도 아닌 디자인 패턴으로 안드로이드에만 적용되는 것은 아니다. 아래에서 dagger 개념을 일부 설명하는데, 꼭 dagger 지식이 있어야만 hilt를 쓸 수 없는 것은 아니지만 사용에 큰 도움이 되는 것은 사실이다.

Dependency Intection이란 무엇일까?

간단히 말해서 어떠한 클래스의 변수나 메서드를 그 클래스를 인스턴스화(객체화)하지 않고 쓰는 디자인 패턴이다. 우리가 사용하는 클래스나 jetpack의 기능들은 다른 클래스나 기능에 의존하고 있는 경우가 꽤 많다.

예를 들어 glide는 HTTP 라이브러리나 메서드를 내부적으로 포함하고 있기에 이에 의존적이고, Recyclerview도 adapter를 만들어 adapter에 리사이클러뷰를 적용하여 사용하고 있고,
MVVM의 경우에도 view가 viewModel에 의존적인 사례이다.


위 그림과 같이 Car클래스는 Engine, Wheel 클래스에 의존적이라 생성자에서 이들을 불러 만들어진다.

따라서 이 Car를 불러와 사용하려면 안에 들어가는 클래스 타입의 객체들을 모두 선언하여 안에 넣어주는 작업이 필요하다.

하지만 hilt를 통한 의존성 주입을 한다면 car 및 car이 의존하는 클래스들을 객체화 하는 일 없이 바로 메소드를 사용할 수 있다.
출처 : https://www.udemy.com/course/hiltandroid/

Dagger2 abstract

  • Module
    어떻게 우리가 인스턴스를 만드는지에 대한 정보를 적는다. 이는 @Provides 애너테이션 밑에 우리가 필요한 객체의 리턴 타입을 리턴해 줌으로서 작성한다.

    • 즉 instantiation 로직을 적어주는 부분이다.
  • Component
    실제로 우리가 인스턴스를 사용하는 Target과 Module 사이의 다리 역할을 한다고 볼 수 있다. 그래서 무엇을 inject 할 것인지, 어디에 inject 할 것인지를 inject fun을 component에 적어준다.

    • 즉 injection 로직을 적어주는 부분이다.
  • Target
    component를 사용해 필요한 곳에 인스턴스를 @inject 한다.

Hilt는 component를 작성하는 과정을 없애주어 좀 더 유저가 수월하게 의존성 주입을 하도록 도와준다.

초기세팅

hilt는 project단과 module:app 단에서 모두 dependency 설정을 해줘야 한다.

project

// 이전 버전의 경우
buildscript {
    repositories {
        google()

    }
    dependencies {
        classpath 'com.google.dagger:hilt-android-gradle-plugin:2.42' //hilt
    }
}


// 최근 버전의 경우
plugins {
    id 'com.android.application' version '7.3.1' apply false
    id 'com.android.library' version '7.3.1' apply false
    id 'org.jetbrains.kotlin.android' version '1.7.20' apply false
    id 'com.google.dagger.hilt.android' version '2.42' apply false //hilt

}

module

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    id 'kotlin-android'
    id 'kotlin-kapt'
    id 'dagger.hilt.android.plugin'
}
...
dependencies{
...
implementation "com.google.dagger:hilt-android:2.43.2"
    kapt "com.google.dagger:hilt-compiler:2.40.5"
 

Application Class 생성

hilt 에서는 application class를 필수적으로 만들어줘야 한다.
안의 내용이 비어있어도 된다.

많은 모듈이 있는 프로젝트의 경우 application class는 다른 모듈들의 dependency를 가진 모듈 안에 선언되어 있어야 한다. 이 클래스는
@HiltAndroidApp 애너테이션 써준다.

HiltTutorialAplication

@HiltAndroidApp
class HiltTutorialApplication : Application() {
}

만들어주고 manifest 파일의 android:name에 이 클래스를 등록해준다.

android:name=".HiltTutorialApplication"

AndroidEntryPoint

의존성이 어디에 provide 되는지를 써놓는 곳이 androidEntryPoint 이다.
androidEntryPoint로 애너테이션하는 프래그먼트나 클래스를 사용하는 다른 클래스 역시 androidEntryPoint로 애너테이션을 해줘야 한다.

Hilt가 지원하는 application, activity, fragment는
@HiltAndroidApplication으로 묶인 application,
ComponentActivity인 액티비티,
androidx.fragment의 형태인 fragment이다.

Constructor injection

component를 dependency graph에 넣는 것이다.

class MyClass @Inject constructor()

와 같이 선언하며 이 myClass를 필요한 곳에 넣어줄 수 있어진다.

class MyClass @Inject constructor(dependency : Dependency)
class Dependency @Inject constructor()

위와 같이 생성자에 매개변수를 넣을 수도 있으며, Dependency가 @Inject로 애너테이션이 되어있으니 이 Dependency를 매개변수로 MyClass가 가져온 것이다.

간단히 예를 들어 써보자.

DatabaseService

class DatabaseService @Inject constructor(){

    fun log(message : String){
        Log.d(TAG,"Database Service $message")
    }
}

databaseservice 클래스에서는 constructor로 받는 매개변수 없이 application에서 선언한 TAG와 함께 메시지를 출력하는 함수가 들어있다.

HiltTutorialApplication

const val TAG = "HiltTutorialLogTag"

@HiltAndroidApp
class HiltTutorialApplication : Application() {
}

DataBaseAdapter

class DatabaseAdapter @Inject constructor(var dataBaseService: DatabaseService){
    fun log(message : String){
        Log.d(TAG, "DatabaseAdapter : $message")
        dataBaseService.log(message)
    }
}

databaseAdapter에서는 dataBaseService를 받아서 이를 인스턴스화하여 안의 log 함수를 불러온다. dataBaseService의 inject 애너테이션이 있어 가능한 일이다.

Field Injection : Activity에서 사용하기

Field Injection은 constructor Injection으로 의존성 그래프에 속한 클래스를 불러오는 것이다.

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var databaseAdapter: DatabaseAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        Log.d(TAG, "dataBaseAdapter : $databaseAdapter")
        databaseAdapter.log("Hello Hilt")
    }
}

MainActivity에서는 databaseAdapter 사용을 굳이 인스턴스화 하지 않고

@Inject
    lateinit var databaseAdapter: DatabaseAdapter

위와 같이 선언함으로써 hilt가 자동으로 인스턴스를 만들어주어서 내부 메서드를 사용할 수 있도록 해준다.

Method Injection

method injection은 field injection의 지름길 사용방법 같은 것인데,

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var databaseAdapter: DatabaseAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        Log.d(TAG, "dataBaseAdapter : $databaseAdapter")
        databaseAdapter.log("Hello Hilt")
    }

    @Inject
    fun directToDatabase(databaseService: DatabaseService){
        databaseService.log("Method Injection")
    }
}

아래 inject애너테이션으로 된 부분을 보면 dataBaseService를 인자로 받아 안의 메서드를 사용하는 함수를 만들었다. 이를 실행하면 hilt가 databaseService를 의존성에 주입함과 함께 directToDatabase 함수가 실행되게 된다.

oncreate함수에서 별도로 directToDatabase를 부르는 실행문을 선언하지 않았는데도 directToDatabase가 invoke가 된다.

profile
컴퓨터와 교육 그사이 어딘가

2개의 댓글

comment-user-thumbnail
2023년 9월 4일

와 좋은데요?

1개의 답글