본문 바로가기

안드로이드

[Android/Kotlin] Hilt 사용하기 !

728x90

아주 간단하게 hilt를 사용해서 앱 구현을 해보도록 하겠습니다. 

 

이것을 구현하기 전에 DI에 대해서 알고있으면 좋기때문에 이 글을 읽고 오시는 것을 추천합니다. 

https://jangstory.tistory.com/58

 

[Android] Dependency Injection. DI 에 대해서

Dependency Injection 에 대해서 정리를 해보도록 하겠습니다 . https://developer.android.com/training/dependency-injection Android의 종속 항목 삽입  | Android 개발자  | Android Developers Android의..

jangstory.tistory.com

 

준비 

https://developer.android.com/training/dependency-injection/hilt-android?hl=ko 

 

Hilt를 사용한 종속 항목 삽입  |  Android 개발자  |  Android Developers

Hilt를 사용한 종속 항목 삽입 Hilt는 프로젝트에서 수동 종속 항목 삽입을 실행하는 상용구를 줄이는 Android용 종속 항목 삽입 라이브러리입니다. 수동 종속 항목 삽입을 실행하려면 모든 클래스

developer.android.com

 

 

plugins 추가 

plugins {
	.
	.
    id 'kotlin-kapt'
    id 'dagger.hilt.android.plugin'
}

build.gradle(Module) 추가해주세요. 

implementation "androidx.room:room-runtime:2.3.0"
kapt "androidx.room:room-compiler:2.3.0"
implementation "com.google.dagger:hilt-android:2.40"
kapt "com.google.dagger:hilt-android-compiler:2.40"

 

build.gradle(Project) 추가해주세요. 

classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'

 

 

시작 ! 

Hilt 애플리케이션 클래스

Hilt를 사용하는 모든 앱은 @HiltAndroidApp으로 주석이 지정된 Application 클래스를 포함해야 합니다.

@HiltAndroidApp은 애플리케이션 수준 종속 항목 컨테이너 역할을 하는 애플리케이션의 기본 클래스를 비롯하여 Hilt의 코드 생성을 트리거합니다.

@HiltAndroidApp
class ExampleApplication : Application()

그 후에 메니페스트에 등록을 해줍니다. 

<application
    android:name=".ExampleApplication"  //<- 이부분 

</application>

 

여기까지 하셨다면 일단 준비는 모두 끝난 상태입니다! 

 

 

 

 

Android 클래스에 종속 항목 삽입

Application 클래스에 Hilt를 설정하고 애플리케이션 수준 구성요소를 사용할 수 있게 되면 Hilt는 @AndroidEntryPoint 주석이 있는 다른 Android 클래스에 종속 항목을 제공할 수 있습니다.

 

Hilt 결합 정의

필드 삽입을 실행하려면 Hilt가 해당 구성요소에서 필요한 종속 항목의 인스턴스를 제공하는 방법을 알아야 합니다. 결합에는 특정 유형의 인스턴스를 종속 항목으로 제공하는 데 필요한 정보가 포함됩니다.

Hilt에 결합 정보를 제공하는 한 가지 방법은 생성자 삽입입니다. 다음과 같이 클래스의 생성자에서 @Inject 주석을 사용하여 클래스의 인스턴스를 제공하는 방법을 Hilt에 알려줍니다.

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject lateinit var navigator :AppNavigator // 이 AppNavigator 를 결합

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding =ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)


        if (savedInstanceState == null) {
            navigator.navigateTo(Screens.FRAGMENT1)
        }
    }
}

 

activity_main.xml 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <FrameLayout
        android:id="@+id/main_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

 

 

 

Hilt 모듈

때로 유형을 생성자 삽입할 수 없는 상황도 있습니다. 이러한 상황은 여러 가지 이유로 인해 발생할 수 있습니다. 예를 들어 인터페이스를 생성자 삽입할 수 없습니다. 또한 외부 라이브러리의 클래스와 같이 소유하지 않은 유형도 생성자 삽입할 수 없습니다. 이럴 때는 Hilt 모듈을 사용하여 Hilt에 결합 정보를 제공할 수 있습니다.

 

Hilt 모듈은 @Module로 주석이 지정된 클래스입니다. Dagger 모듈과 마찬가지로 이 모듈은 특정 유형의 인스턴스를 제공하는 방법을 Hilt에 알려줍니다. 그러나 Dagger 모듈과는 달리, Hilt 모듈에 @InstallIn 주석을 지정하여 각 모듈을 사용하거나 설치할 Android 클래스를 Hilt에 알려야 합니다.

 

 

@Binds를 사용하여 인터페이스 인스턴스 삽입

Interface 생성 

AppNavigator class 

enum class Screens {
    FRAGMENT1,
    FRAGMENT2
}

interface AppNavigator {
    fun navigateTo(screen: Screens)
}

 

AppNavigatorImpl class 생성 

class AppNavigatorImpl @Inject constructor(private val activity: FragmentActivity) : AppNavigator {

    override fun navigateTo(screen: Screens) {
        val fragment = when (screen) {
            Screens.FRAGMENT1 -> Fragment1()
            Screens.FRAGMENT2 -> Fragment2()
        }

        activity.supportFragmentManager.beginTransaction()
            .replace(R.id.main_container, fragment)
            .addToBackStack(fragment::class.java.canonicalName)
            .commit()
    }
}

 

앞에 생성한 2개의 클래스를 이어줄 module 생성 

@InstallIn(ActivityComponent::class)
@Module
abstract class NavigationModule {

    @Binds
    abstract fun bindNavigator(impl: AppNavigatorImpl): AppNavigator
}

 

 

이 외에 역할이 다는 @Provides 를 사용한 인스턴스 삽입이 있는데 오늘 예제에서는 하지 않겠습니다! 

다만 @Provides 삽입을 할 때는 아래와 같을 때 사용한다고 되어있습니다 .

@Provides를 사용하여 인스턴스 삽입

인터페이스가 유형을 생성자 삽입할 수 없는 유일한 경우는 아닙니다. 클래스가 외부 라이브러리에서 제공되므로 클래스를 소유하지 않은 경우(Retrofit, OkHttpClient 또는 Room 데이터베이스와 같은 클래스) 또는 빌더 패턴으로 인스턴스를 생성해야 하는 경우에도 생성자 삽입이 불가능합니다.

이전 예를 생각해 보세요. AnalyticsService 클래스를 직접 소유하지 않으면 Hilt 모듈 내에 함수를 생성하고 이 함수에 @Provides 주석을 지정하여 이 유형의 인스턴스를 제공하는 방법을 Hilt에 알릴 수 있습니다.

 

 

 

 

마찬가지로 @AndroidEndPoint 넣어주고 

@AndroidEntryPoint
class Fragment1 : Fragment() {



    @InMemoryLogger
    @Inject lateinit var logger: LogInterface
    @Inject lateinit var navigator: AppNavigator

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        val binding = Fragment1Binding.inflate(inflater ,container,false)

 
        binding.btnClick2.setOnClickListener {
            navigator.navigateTo(Screens.FRAGMENT2)
        }


        return binding.root
    }

}

 

 

 

xml 

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.Fragment1">


    <Button
        android:id="@+id/btn_click2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="240dp"
        android:text="Click2"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.498"
        app:layout_constraintStart_toStartOf="parent" />


</androidx.constraintlayout.widget.ConstraintLayout>

 

 

Fragment2 

 

@AndroidEntryPoint
class Fragment2 : Fragment() {


    private lateinit var binding :Fragment2Binding

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        binding = Fragment2Binding.inflate(inflater, container, false)


        return binding.root
    }
}

 

 

xml 

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    tools:context=".ui.Fragment2">
    
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Fragment2"/>
</FrameLayout>

 

 

지금까지 아주 간단한 Hilt를 사용한 예제를 가져와봤습니다. 

 

추가로 아래의 내용도 공부하시면 좋을것 같습니다. 감사합니다! 

 

Android 클래스용으로 생성된 구성요소

필드 삽입을 실행할 수 있는 각 Android 클래스마다 @InstallIn 주석에 참조할 수 있는 관련 Hilt 구성요소가 있습니다. 각 Hilt 구성요소는 해당 Android 클래스에 결합을 삽입해야 합니다.

이전 예에서는 Hilt 모듈에서 ActivityComponent를 사용하는 방법을 보여주었습니다.

Hilt는 다음 구성요소를 제공합니다.

Hilt 구성요소인젝터 대상
ApplicationComponent Application
ActivityRetainedComponent ViewModel
ActivityComponent Activity
FragmentComponent Fragment
ViewComponent View
ViewWithFragmentComponent @WithFragmentBindings 주석이 지정된 View
ServiceComponent Service

참고: Hilt는 ApplicationComponent에서 직접 broadcast receiver를 삽입하므로 broadcast receiver의 구성요소를 생성하지 않습니다.

구성요소 전체 기간

Hilt는 해당 Android 클래스의 수명 주기에 따라 생성된 구성요소 클래스의 인스턴스를 자동으로 만들고 제거합니다.

생성된 구성요소생성 위치제거 위치
ApplicationComponent Application#onCreate() Application#onDestroy()
ActivityRetainedComponent Activity#onCreate() Activity#onDestroy()
ActivityComponent Activity#onCreate() Activity#onDestroy()
FragmentComponent Fragment#onAttach() Fragment#onDestroy()
ViewComponent View#super() 제거된 뷰
ViewWithFragmentComponent View#super() 제거된 뷰
ServiceComponent Service#onCreate() Service#onDestroy()

참고: ActivityRetainedComponent는 구성 변경 전체에 걸쳐 유지되므로 첫 번째 Activity#onCreate()에서 생성되고 마지막 Activity#onDestroy()에서 제거됩니다.

구성요소 범위

기본적으로 Hilt의 모든 결합은 범위가 지정되지 않습니다. 즉, 앱이 결합을 요청할 때마다 Hilt는 필요한 유형의 새 인스턴스를 생성합니다.

이 예에서 Hilt는 다른 유형의 종속 항목으로 또는 필드 삽입을 통해(ExampleActivity에서와 같이) AnalyticsAdapter를 제공할 때마다 AnalyticsAdapter의 새 인스턴스를 제공합니다.

그러나 Hilt는 결합을 특정 구성요소로 범위 지정할 수도 있습니다. Hilt는 결합의 범위가 지정된 구성요소의 인스턴스마다 한 번만 범위가 지정된 결합을 생성하며, 이 결합에 관한 모든 요청은 동일한 인스턴스를 공유합니다.

아래 표에는 생성된 각 구성요소의 범위 주석이 나와 있습니다.

Android 클래스생성된 구성요소범위
Application ApplicationComponent @Singleton
View Model ActivityRetainedComponent @ActivityRetainedScope
Activity ActivityComponent @ActivityScoped
Fragment FragmentComponent @FragmentScoped
View ViewComponent @ViewScoped
@WithFragmentBindings 주석이 지정된 View ViewWithFragmentComponent @ViewScoped
Service ServiceComponent @ServiceScoped

이 예에서 @ActivityScoped를 사용하여 AnalyticsAdapter의 범위를 ActivityComponent로 지정하면 Hilt는 해당 활동의 수명 주기 동안 동일한 AnalyticsAdapter 인스턴스를 제공합니다.