Android 기존 프로젝트에 hilt 적용하기

Jiyoon Chae·2025년 4월 22일
0

안드로이드

목록 보기
21/23

? android hilt적용하려고 하니까, 무슨 모듈에 등록을해야한대. repositoryModule에 등록하기

@Binds
    abstract fun bindSearchHomeRepository(repo: SearchHomeRepositoryImpl): SearchHomeRepository

Hilt 를 Android 프로젝트에 적용할 때 @Binds를 사용하여 repository를 모듈에 등록한다.
1. Repository Module이란?

  • Repository Module은 Hilt의 의존성 주입 시스템에서 사용되는 모듈 클래스 이다.
    이 모듈은 주로 애플리케이션의 데이터 레이어에 관련된 의존성들을 제공하는 역할을 한다.
    구체적으로
  • 데이터 소스와 관련된 Repository 인터페이스와 구현체를 연결한다.
interface SearchHomeRepository {
	API 호출이 필요한 메소드들 추가 
}

class SearchHomeRepositoryImpl @Inject constructor(@NetworkModule.GWNetworkAPI private val apiService: Lazy<RetrofitService>) : SearchHomeRepository {
	override로 해당 메소드들 구현 
}
  • 일반적으로 @Module 과 @InstallIn 어노테이션이 붙은 abstract 클래스로 정의 된다.
@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {
	@Binds
    abstract fun bindSearchHomeRepository(repo: SearchHomeRepositoryImpl): SearchHomeRepository
}
  • 앱의 의존성 주입 그래프에 Respository 관련 바인딩을 제공한다.
  1. 코드 설명
@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {
	@Binds
    abstract fun bindSearchHomeRepository(repo: SearchHomeRepositoryImpl): SearchHomeRepository
}
  • @Binds : Hilt/Dagger에서 인터페이스와 그 구현체를 연결해주는 어노테이션.
    이것은 "SearchHomeRepository 타입이 필요할 때, SearchHomeRepositoryImpl 인스턴스를 제공하라" 는 의미!!

-abstract fun : @Binds 는 항상 abstract 함수에 사용된다. 실제 구현은 Hilt 가 컴파일 타임에 생성.

  • bindSearchHomeRepository : 함수 이름. 관례적으로 bind접두사를 붙이지만 기능적으로 이름이 중요한건 아님.

  • repo : SearchHomeRepositoryImpl : 이 매개변수는 Hilt가 제공할 구현체입니다. Hilt는 이 타입의 인스턴스를 어떻게 생성할지 알아야한다(보통 생성자 주입을 통해)

  • SearchHomeRepository : 반환 타입은 바인딩하려는 인터페이스!! 이것은 의존성 주입 시 실제로 요청하는 타입.

  1. 의존성 주입을 사용하는 이유 !
  • 관심사 분리 : 인터페이스(SearchHomeRepository) 와 구현체(SearchHomeRepositoryImpl)를 분리하여 코드의 결합도를 낮춤.
  • 테스트 용이성 : 실제 구현체 대신 모의객체 mock 로 쉽게 교체할 수 있어 단위 테스트가 용이해짐.
  • 유지보수성 : 구현체를 변경해도 사용하는 코드는 수정할 필요가 없음.
  • 확장성 : 다양한 구현체를 쉽게 스위칭 할 수 있음 (예: 개발용 / 프로덕션용 )
  1. 전체 RepositoryModule
  • @InstallIn : 이 모듈이 어떤 Hilt 컴포넌트에 설치될 지 지정하는것.
    (SingletonComponent::class) 는 앱의 생명주기 동안 단일 인스턴스로 유지됨을 의미!
    사용 시에는 다음과 같이 의존성을 주입받아 사용한다.
class SearchViewModel @Inject constructor(
    private val searchHomeRepository: SearchHomeRepository
) {
    // SearchHomeRepository 인터페이스를 사용하지만, 
    // 실제로는 SearchHomeRepositoryImpl이 주입됩니다
}

-> 이렇게 하면 구체적인 구현에 의존하지 않고, 인터페이스에만 의존하게 되어 유연하고 테스트 하기 쉬운 코드를 작성할 수 있음!

프래그먼트에 @AndroidEntryPoint 를 추가
-> @AndroidEntryPoint 어노테이션은 Hilt를 Android 컴포넌트에 적용하기 위한 핵심 요소.
이 어노테이션을 Fragment에 추가하면 해당 Fragment에서 Hilt의 의존성 주입 기능을 사용할 수 있게 된다.

@AndroidEntryPoint의 역할과 기능
1. Hilt 의존성 주입 활성화: 이 어노테이션이 적용된 Fragment는 Hilt의 의존성 주입 시스템에 참여할 수 있게 된다.
2. 코드 생성 트리거: 컴파일 시점에 Hilt는 이 어노테이션을 보고 필요한 의존성 주입 코드를 자동으로 생성.
3. 부모 컴포넌트 연결: Fragment의 부모인 Activity도 @AndroidEntryPoint로 표시되어 있어야 하며, Hilt는 이러한 계층 구조를 인식하여 적절한 의존성 그래프를 구성하게됨.

@AndroidEntryPoint
class SearchHomeFragment : Fragment() {
    
    // @Inject 어노테이션으로 의존성 주입
    @Inject
    lateinit var viewModelFactory: ViewModelProvider.Factory
    
    // 또는 by viewModels()를 사용해 ViewModel 주입받기
    private val viewModel: SearchViewModel by viewModels()
    
    // Fragment 생명주기 메서드들...
    override fun onCreateView(...) { ... }
}

주요 특징

  1. 자동 의존성 주입: Fragment 내부에서 @Inject 어노테이션을 사용하여 필요한 의존성을 직접 주입받을 수 있다.
  2. ViewModel 통합: Hilt의 by viewModels() 확장 함수를 사용하여 ViewModel을 쉽게 주입받을 수 있습니다.
  3. 생명주기 인식: Hilt는 Fragment의 생명주기를 인식하여 적절한 시점에 의존성을 주입하고 관리한다.

중요 고려사항

  1. 부모 Activity 요구사항: Fragment를 포함하는 Activity도 반드시 @AndroidEntryPoint로 표시되어 있어야 함.
  2. 계층적 의존성: Hilt의 컴포넌트 계층 구조에 따라 Fragment는 자신의 부모 Activity에서 사용 가능한 의존성에도 접근할 수 있음!!
  3. Application 설정: 앱의 Application 클래스에는 @HiltAndroidApp 어노테이션이 필요하다.

뷰모델 선언 수정
: @AndroidEntryPoint와 Hilt를 적용한 후에는 기존의 ViewModel 초기화 방식을 더 간단하게 변경할 수 있다. 기존에 사용하시던 코드는:

private val vm: SearchViewModel by lazy { ViewModelProvider(this)[SearchViewModel::class.java] }

이 코드는 전통적인 방식으로 ViewModel을 초기화하는 방법.. Fragment가 생성될 때 lazy 초기화를 통해 ViewModelProvider를 사용하여 SearchViewModel의 인스턴스를 가져온다.
Hilt와 @AndroidEntryPoint를 적용한 후에는 이 코드를 다음과 같이 더 간결하게 변경할 수 있다

private val vm: SearchViewModel by viewModels()

by viewModels()와 기존의 ViewModelProvider 방식은 모두 ViewModel을 초기화하는 방법이지만 차이점이 있음.
1. by viewModels()

  • by viewModels()는 Android Jetpack의 androidx.fragment.app.Fragment 및 androidx.activity.ComponentActivity 클래스에서 제공하는 프로퍼티 위임(property delegate).
    by viewModels()를 사용하면 Kotlin의 프로퍼티 위임 기능을 사용하여 ViewModel 인스턴스의 생성과 접근을 관리한다.
  1. 주요 차이점
    1. 코드 간소화: 훨씬 더 짧고 읽기 쉬운 코드가 됨.
    2. 자동 의존성 주입: SearchViewModel의 생성자에 @Inject 어노테이션을 사용하여 필요한 의존성을 자동으로 주입받을 수 있음.
      • 기존 방식: Hilt와의 통합이 직접적이지 않다. ViewModel에 의존성을 주입하려면 Factory 패턴을 사용해야 함.
      • by viewModels(): @HiltViewModel 어노테이션과 완벽하게 통합되어, 선언만으로도 Hilt가 자동으로 의존성을 주입한다.
    3. 기능 확장성
      • 기존 방식: 추가 파라미터를 전달하려면 커스텀 ViewModelProvider.Factory를 구현해야됨..
      • by viewModels(): 다양한 옵션 파라미터 제공
// 예: 부모 Fragment와 ViewModel 공유
private val sharedVM: SharedViewModel by viewModels({ requireParentFragment() })

// 예: 커스텀 팩토리 제공
private val vm: SearchViewModel by viewModels { customFactory }
4. SavedStateHandle 지원

기존 방식: SavedStateHandle을 사용하려면 AbstractSavedStateViewModelFactory 등의 복잡한 설정이 필요...
by viewModels(): SavedStateHandle이 필요한 ViewModel에 자동으로 주입됨!

5. 테스트 용이성: 테스트 시 가짜(fake) 또는 모의(mock) 의존성을 쉽게 교체할 수 있습니다.
6. 스코프 관리: ViewModel의 생명주기가 Fragment 또는 Activity의 생명주기에 맞게 자동으로 관리됩니다.
  1. by viewModels() 동작 원리

-지연 초기화: 프로퍼티에 처음 접근할 때 ViewModel 인스턴스가 초기화됨.
-생명주기 인식: Fragment나 Activity의 생명주기에 맞춰 ViewModel 인스턴스를 관리한다.
-구성 변경 유지: 화면 회전 등의 구성 변경 시에도 ViewModel 인스턴스를 유지!!!
-ViewModelStoreOwner 활용: 내부적으로 Fragment나 Activity의 ViewModelStore를 사용하여 ViewModel 인스턴스를 저장.

  1. by viewModels()의 추가 장점
    -Fragment 간 ViewModel 공유: activityViewModels()를 사용하여 Activity 범위의 ViewModel을 쉽게 공유할 수 있습니다.
    -커스텀 ViewModelProvider.Factory: viewModels { factory } 구문으로 필요시 커스텀 팩토리를 제공할 수 있습니다.
    -ViewModel 키 지정: 같은 타입의 여러 ViewModel 인스턴스가 필요할 때 키를 지정할 수 있습니다: viewModels(key = "custom_key")

이렇게 변경하려면 SearchViewModel 클래스도 다음과 같이 수정해야 됨:

@HiltViewModel
class SearchViewModel @Inject constructor(
    private val searchHomeRepository: SearchHomeRepository
    // 기타 필요한 의존성들...
) : ViewModel() {
    // ViewModel 코드
}

@HiltViewModel 어노테이션은 Hilt에서 ViewModel을 위해 특별히 제공하는 어노테이션이다. 이 어노테이션을 사용함으로써 Hilt가 Android의 ViewModel 아키텍처 컴포넌트와 통합되어 작동하도록해줌!

@HiltViewModel 어노테이션의 역할

1.ViewModel 의존성 주입 활성화: 이 어노테이션이 있는 ViewModel 클래스는 Hilt의 의존성 주입 시스템에 참여하게됨.
2.ViewModelComponent 생성: Hilt는 이 어노테이션이 붙은 ViewModel을 위한 특별한 컴포넌트를 생성하여 ViewModel 범위의 의존성을 관리한다.
3. SavedStateHandle 지원: 자동으로 SavedStateHandle을 주입할 수 있도록 지원.
4. by viewModels() 연결: @AndroidEntryPoint로 표시된 Fragment나 Activity에서 by viewModels()를 사용해 접근할 수 있게 됨.

위의 코드 분석
이 코드를 분석해보면:

  • @HiltViewModel: 이 ViewModel이 Hilt에 의해 관리됨을 나타냅니다.
  • @Inject constructor: 생성자 주입을 통해 의존성을 받습니다.
  • searchHomeRepository: RepositoryModule에서 제공하는 SearchHomeRepository 인터페이스의 구현체가 주입됨.

사용 이점

1.생명주기 관리: ViewModel의 생명주기를 Android 아키텍처 컴포넌트와 일치시킨다.
2.스코프 관리: ViewModel 인스턴스는 해당 Fragment/Activity의 생명주기 동안 유지된다.
3.테스트 용이성: 테스트 환경에서 쉽게 가짜 의존성을 주입할 수 있다.
4.의존성 그래프 통합: Hilt의 전체 의존성 그래프에 ViewModel이 통합된다

@HiltViewModel 사용 시 중요 사항

1.SavedStateHandle 사용: 필요한 경우 다음과 같이 SavedStateHandle을 주입받을 수 있다:

@HiltViewModel
class SearchViewModel @Inject constructor(
    private val searchHomeRepository: SearchHomeRepository,
    private val savedStateHandle: SavedStateHandle
) : ViewModel() { ... }

2.ViewModelComponent 스코프: @HiltViewModel이 적용된 ViewModel은 ViewModelComponent에 설치된 모듈의 의존성에 접근할 수 있다.
3.Fragment/Activity 연결: 이 ViewModel을 사용하는 Fragment나 Activity는 반드시 @AndroidEntryPoint로 표시되어 있어야 한다.

요약하자면, @HiltViewModel은 Android의 ViewModel 시스템과 Hilt의 의존성 주입 시스템을 매끄럽게 통합하여, 깔끔하고 테스트 가능한 아키텍처를 구현할 수 있게 해주는 중요한 어노테이션!

profile
바닐라라떼 좋아☕

0개의 댓글