이동식 저장소

[Hilt] Android Entry Points 본문

Primary/Android

[Hilt] Android Entry Points

해스끼 2022. 8. 11. 21:28

주의: 이 블로그에서 말하는 바인딩은 ``@Binds``와 ``@Provides`` 모두를 의미합니다.

 

Entry Point란 변수를 주입받을 수 있는 클래스를 말한다. Hilt의 모태인 dagger에도 존재하는 유서깊은 개념이다.

 

``Application``에서 의존성 주입 기능을 활성화했다면 다른 안드로이드 클래스에서 변수를 주입받을 수 있다. 그런데 모든 클래스에서 주입받을 수 있는 건 아니고, 다음 클래스에서만 멤버를 주입받을 수 있다.

  • Activity (``ComponentActivity``)
  • Fragment (androidx ``Fragment``)
  • View
  • Service
  • BroadcastReceiver
  • ViewModel

사실 ``ViewModel``은 다른 5개의 클래스와는 약간 다른 방식이 사용된다. 다른 글에서 자세히 얘기하겠다. ContentProvider도 직접 지원하지는 않지만, dagger의 entry point에 직접 접근하면 멤버를 주입받을 수 있다. 여기서는 생략.

Android Entry Point와 dagger의 Entry Point는 다르다. 정확히는 dagger의 Entry Point가 상위의 개념이다.

다음은 ``Activity``에서 Hilt로 변수를 주입받는 방법이다. 다른 클래스에서도 똑같이 하면 된다.

@AndroidEntryPoint
class HomeActivity: ComponentActivity() {
    // 바인딩이 SingletonComponent 또는 ActivityComponent에 설치되어야 한다.
    @Inject lateinit var foo: Foo
    
    override fun onCreate(savedInstance: Bundle?) {
        // 여기서 주입된다.
        super.onCreate()
        
        // onCreate() 이후 사용 가능
        foo.doSomething()
    }
}

클래스 최상단에 ``@AndroidEntryPoint`` 어노테이션을 붙이고, 주입받을 변수에 ``@Inject``를 붙이면 된다. 

 

단, 해당 객체를 제공하는 모듈이 적절한 component에 존재해야 한다. 위의 코드에서 ``Foo`` 타입을 제공하는 바인딩은 ``SingletonComponent`` 또는 ``ActivityComponent``에 설치되어야 한다. 하위 component에 설치하면 Hilt가 바인딩을 못 찾는다.

 

참고로 component마다 객체가 주입되는 위치가 다르다. 

Retained Fragments

Fragment의 ``onCreate`` 함수 안에서 ``setRetainInstance(true)``를 호출하면 해당 fragment가 configuration change에서 살아남는다. 진짜? 처음 알았다.

 

그런데 Hilt를 사용하는 fragment는 절대로 retain되면 안 된다. 왜냐하면 Hilt를 사용하는 fragment는 fragment 안에서 사용된 component를 참조하는데, 이 component가 사실 ActivityComponent일 수도 있기 때문이다. 자식이 부모를 참조하는 이상한 일이 벌어지는 것이다. 또, scope된 바인딩과 provider 중 fragment에 주입된 것들이 메모리 누수를 일으킬 수 있다.

 

추측하기로는 Hilt에서 fragment가 retain되는지 아닌지 판단할 방법이 없는 듯하다. 구조적으로 불가능하다던가? 어쨌든 이런 문제에 대응하기 위해 Hilt fragment가 retain되면 exception이 발생한다. 

 

물론 Hilt를 사용하지 않는 fragment는 얼마든지 retain될 수 있지만, 그마저도 자식 fragment에서 Hilt를 사용한다면 exception이 발생할 것이다.

 

더보기

# (클릭) Fragment가 retain되는 매커니즘

 

Configuration change가 발생하면 화면의 모든 activity와 fragment가 새로 만들어진다. 그런데 retain을 활성화하면 해당 fragment만 살아남아 새로운 activity에 부착된다. 위에서 말한 모든 문제는 fragment가 새로운 activity에 부착될 때 일어난다.

 

그래서 같은 activity 객체에 다시 attach되는 건 상관없다. 하지만 그냥 새 fragment를 만드는 게 낫다. 헷갈리지도 않고.

View에서 Fragment 바인딩을 사용하는 방법

View에서는 기본적으로 ``SingletonComponent``와 ``ActivityComponent``의 바인딩만을 사용할 수 있다. ``FragmentComponent``의 바인딩을 사용하고 싶다면 ``@WithFragmentBindings`` 어노테이션을 붙여야 한다.

@AndroidEntryPoint
@WithFragmentBindings
class MyView : MyBaseView {
  // SingletonComponent, ActivityComponent,
  // FragmentComponent, ViewComponent의 바인딩 사용 가능
  @Inject lateinit var bar: Bar

  constructor(context: Context) : super(context)
  constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)

  init {
    // bar 사용 가능
    bar.doSomething()
  }

  override fun onFinishInflate() {
    super.onFinishInflate();

    // 다른 뷰 작업
  }
}

'Primary > Android' 카테고리의 다른 글

[Hilt] Modules  (0) 2022.08.29
[Hilt] ViewModels  (0) 2022.08.29
[Hilt] Application  (0) 2022.08.11
[Android] 계정명이 한글일 때 test error 해결법  (0) 2022.07.26
[Android] Manifest 파일이란?  (0) 2022.07.25
Comments