ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • (DI - 3편) DI 라이브러리 Hilt Annotations
    안드로이드 학습/Android 기술면접 대비 2024. 11. 16. 12:55

    의존성 학습 시리즈 3편 : 
    (DI - 1편) 의존성 주입이란
    (DI - 2편) 안드로이드 의존성 수동 주입
    (DI - 3편) DI 라이브러리 Hilt

     

    코틀린을 수동으로 주입시켜줄수도 있지만 굳이 이런 과정 필요없이 더 간단하게 하는 방법은 제공되는 라이브러리를 사용하는 것이다. 

    • Hilt :Google의 Dagger를 기반으로 하여 만든 의존성 주입 라이브러리로 안드로이드에 특화된 라이브러리
    • Dagger2 : Java/kotlin을 위한 의존성 주입 프레임 워크이다. 컴파일 단계에서 의존성을 주입
    • Koin : 순수 kotlin으로 작성된 의존성 주입 프레임 워크이다. dagger2와 달리 런타임때 의존성을 주입

    우선 안드로이드에서는 Hilt를 사용하는 것을 추천하기 대문에 Hilt를 먼저 학습하고 나중에 Koin 라이브러리를 학습해봐야겠다. 

     

    Hilt를 어떻게 사용하는지 알아보기 전에 Hilt Annotations를 공부하고 넘어가야 겠다.

    Hilt Annotations

    참고 : 요약본

     

    @HiltAndroidApp

    @HiltAndroidApp
    public class MyApplication extends Application {
        // ...
    }
    • Hilt를 사용하려면 Application에 @HiltAndroidApp 을 포함한 Application class를 사용해야 한다.
    • @HiltAndroidApp이 Application의 생명주기를 참고하여 컴파일 타임 때 필요한 클래스들을 초기화 해주고 의존성 객체를 제공한다.

     

    @AndroidEntryPoint

    @AndroidEntryPoint
    public class MyActivity extends AppCompatActivity {
        @Inject
        SomeDependency someDependency;
        // ...
    }
      • Android Framework 클래스들인 Activity, Fragment, View, Service, BroadcastReceiver 등에 annotation 추가하여 해당 컴포넌트를 의존성 주입 대상으로 지정합니다.
      • ViewModel 도 hilt가 지원하며 @AndroidEntryPoint가 아닌 @HiltViewModel을 사용한다.
      • 프래그먼트에 지정하면 프래그먼트가 띄워진 액티비티에도 추가해야 한다
    더보기
    더보기
    • Application (@HiltAndroidApp을 사용하여)
    • ViewModel (@HiltViewModel을 사용하여)
    • Activity (@AndroidEntryPoint)
    • Fragment (@AndroidEntryPoint)
    • View (@AndroidEntryPoint)
    • Service (@AndroidEntryPoint)
    • BroadcastReceiver (@AndroidEntryPoint)

    @Inject

    class UserRepository @Inject constructor(
        private val apiService: ApiService  // 1. 생성자 사용
    ) {
    
        @Inject
        lateinit var userRepository: UserRepository // 2. 필드 사용
       
       
        private lateinit var userRepository: UserRepository
       
        @Inject
        fun init(otherRepository: OtherRepository) { // 3. 메서드에 사용
            this.otherRepository = otherRepository
        }
    }
    • 이 어노테이션은 주입할 필드, 생성자 또는 메서드에 지정됩니다. Hilt는 해당 필드나 생성자에 대한 인스턴스를 자동으로 생성하고 주입합니다.

    @Module

    • 주입할 객체 타입이 내가 가진 게 아닐 때, construct-inject 가 불가할 때 힐트 모듈을 사용하여 바인딩 정보를 제공
    • interface 또는 내가 구현할 수 없는 클래스(3rd party library - Retrofit, OkHttpClient, Room DB)등에 @Module을 사용한다. 

    @ InstallIn(~::class) :

    • 어느 안드로이드 클래스에 힐트가 사용될지 알려주기 위해 힐트모듈에 사용
    • Hilt에서 생성된 DI 컨테이너(SingletonComponent로 명시된)에서 모듈 바인딩이 반드시 제공되어야 하는 위치를 나타냅니다.

    InstallIn 안에 들어가는 것들

    더보기
    더보기
    • @SingletonComponent
      • 범위: 애플리케이션 전체에서 단일 인스턴스를 공유하는 컴포넌트입니다.
      • 설명: 앱의 생애 주기 동안 하나의 인스턴스만 존재하는 객체를 관리합니다.
    • @ActivityRetainedComponent
      • 범위: Activity가 종료되더라도, Activity가 재생성될 때까지 의존성을 유지하는 컴포넌트입니다.
      • 설명: Activity가 재생성될 때 의존성을 유지해야 할 객체(예: 상태 관리, 비즈니스 로직)가 포함됩니다.
    • @ActivityComponent 
      • 범위: Activity 생애 주기 동안 인스턴스를 유지하는 컴포넌트입니다.
      • 설명: 각 Activity에 대해 인스턴스를 관리합니다. Activity가 종료되면 의존성도 사라집니다.
    • @FragmentComponent
      • 범위: Fragment 생애 주기 동안 인스턴스를 유지하는 컴포넌트입니다.
      • 설명: 각 Fragment에 대해 인스턴스를 관리합니다. Fragment가 종료되면 의존성도 사라집니다.
    • @ViewModelComponent
      • 범위: ViewModel 생애 주기 동안 인스턴스를 유지하는 컴포넌트입니다.
      • 설명: ViewModel에 의존성을 주입할 때 사용됩니다. ViewModel이 클리어될 때 의존성도 사라집니다.
    • @ViewComponent
      • 범위: View의 생애 주기 동안 의존성을 유지합니다.
      • 설명: UI 뷰(예: Custom View)에 대한 의존성 주입에 사용됩니다.
    @Module
    @InstallIn(SingletonComponent::class)
    object AppModule {
        @Provides
        fun provideDatabase(): Database {
            return Database()
        }
    }

     

    @Provides:

     

    Dagger에서 의존성 주입(Dependency Injection)을 설정할 때 사용된다. 이 애노테이션은 특정 타입의 의존성을 제공(provide)하는 방법을 정의하는 메서드에 붙이며, Dagger가 해당 타입의 인스턴스를 생성하거나 반환할 때 호출됩니다.

     

    @Provides를 사용하는 경우 :

    • 직접 생성이 복잡한 경우
      • 생성자에 여러 파라미터가 필요하거나 인스턴스 생성을 위해 추가 로직이 필요한 경우 사용된다.
    • 타사 라이브러리 클래스의 인스턴스를 제공해야 하는 경우
      • 생성자에 접근할 수 없는 클래스(예: Retrofit, OkHttp 등)라면 @Provides 메서드를 활용해 인스턴스를 생성하고 제공해야 한다.
    • 직접 주입할 수 없는 경우
      • 생성자에@Inject를 사용할 수 없는 경우,@Provides를 통해 의존성을 정의합니다.
    @Module
    @InstallIn(SingletonComponent::class)
    class NetworkModule {
    
        @Provides // 다른 곳에서 사용될 객체를 만들어 준다는 뜻이다
        @Singleton
        fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor =
            HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
    }

     

    Retrofit 타입에 의존성을 주입한다.

    @Module
    class NetworkModule {
        @Provides
        fun provideRetrofit(): Retrofit {
            return Retrofit.Builder()
                .baseUrl("https://api.example.com")
                .addConverterFactory(GsonConverterFactory.create())
                .build()
        }
    }

     

     

    @Binds:

     

    Dagger에서 인터페이스 또는 추상 클래스의 구현체를 의존성으로 주입할 때 사용하는 애노테이션이다.

    @Provides와 유사하지만, 추상 메서드를 통해 의존성을 제공한다는 점이 다르다.

     

    @Binds를 사용하는 이유

    • 간결한 코드
      • @Provides를 사용하면 구체적인 구현체를 반환하는 메서드를 작성해야 하지만, @Binds는 더 간단하게 인터페이스와 구현체를 연결합니다.
    • 런타임 비용 감소
      • @Provides는 컴파일 타임이 아니라 런타임에 메서드를 호출해 객체를 반환하지만, @Binds는 컴파일 타임에 더 최적화된 코드를 생성합니다.
    • 인터페이스 의존성 처리
      • @Binds는 인터페이스와 구현체의 관계를 정의할 때 특히 유용합니다.
    // 1. 인터페이스 정의
    interface UserRepository {
        fun getUserData(): String
    }
    
    // 2. 구현체 정의
    class UserRepositoryImpl @Inject constructor() : UserRepository {
        override fun getUserData(): String = "User Data from Repository"
    }
    
    // 3. Binds로 구현체 매핑
    @Module
    abstract class RepositoryModule {
        @Binds
        abstract fun bindUserRepository(
            userRepositoryImpl: UserRepositoryImpl
        ): UserRepository
    }

     

     

    @Provides 와 @Binds 비교 :

    • 공통점
      • @InstallIn로 정의된 모듈 내부에서 종속성 주입을 위해 사용된다.
    • @Binds : 인터페이스와 구현체를 연결하는데에 사용된다.
      • @Binds의 메서드는 추상 메서드여야 한다.고로 abstract 키워드를 요구한다.
      • @Binds 메서드는 동일한 인터페이스에 여러개의 인터페이스 구현체를 연결할 수 없다.
      • 주입될 타입은 인터페이스이다.
    • @Provides : 종속성 객체를 생성, 제공하는 메서드를 정의할 때 사용된다. (@Binds 외의 경우)
      • 종속성을 제공하는데에 의미가 있으므로, 반환값이 존재해야 한다.
      • 복잡한 종속성을 제공하거나 외부 라이브러리를 제공하는데에 사용된다.
        • 외부 라이브러리에는 클래스의 생성자를 직접 수정하거나 @Inject를 추가 할 수 없기 때문이다
      • 주입될 타입은 인터페이스, 클래스 등 다양하다.

     

    외부 라이브러리를 제외하고는 서로 바꿔서 만들 수도 있다. 

    더보기
    더보기
    // 인터페이스 정의
    interface UserRepository {
        fun getUserData(): String
    }
    
    // 구현체 정의
    class UserRepositoryImpl @Inject constructor() : UserRepository {
        override fun getUserData(): String = "User Data from Repository"
    }
    
    // Module 정의
    @Module
    class RepositoryModule {
        @Provides
        fun provideUserRepository(userRepositoryImpl: UserRepositoryImpl): UserRepository {
            return userRepositoryImpl
        }
    }

     

    // 인터페이스 정의
    interface UserRepository {
        fun getUserData(): String
    }
    
    // 구현체 정의
    class UserRepositoryImpl @Inject constructor() : UserRepository {
        override fun getUserData(): String = "User Data from Repository"
    }
    
    // Module 정의
    @Module
    abstract class RepositoryModule {
        @Binds
        abstract fun bindUserRepository(userRepositoryImpl: UserRepositoryImpl): UserRepository
    }

     

Designed by Tistory.