[Dagger] Activity 에서 필드 주입은 언제 일어나야 할까?

H43RO·2022년 12월 11일
8

DI

목록 보기
2/2

본문에 앞서 갑분 자아성찰 타임

세상에.. 마지막 글이 4월이다 🙁

회사 일이 바빠서 라는 핑계는 댈 수가 없다.. 충분히 여기저기 놀러다니고 기타 치고 놀았으면서
과연 블로그 글 쓸 시간이 없었을까?? 이건 100번 반성해야 한다.. 😞
제발 정신줄 꽉 잡고 블로그 활동 좀 하자!! 초심을 잃지 말자 ㅠㅠ

🔥 해당 포스팅은 Dagger 에 대한 기본적인 이해를 전제로 합니다

아래 자료들에 의거하여 작성된 포스팅입니다.
https://developer.android.com/training/dependency-injection/dagger-android?hl=ko
https://dagger.dev/dev-guide/android.html

Android 에서 Dagger 를 사용할 때

Dagger 에서는 여러 DI 기법 중 생성자 주입(Constructor Injection) 을 가장 권장하고 있다. 생성자 주입을 사용한다면, 컴파일러 차원에서 의존성 주입 이전에 객체가 참조되는 등의 상황을 방지해주기 때문이다. (즉, NullPointerException 이 발생하는 상황을 막아준다)

Dagger 문서 中 (링크)

Constructor injection is preferred whenever possible because javac will ensure that no field is referenced before it has been set, which helps avoid NullPointerExceptions.

그런데 일부 상황에선 어쩔 수 없이 Constructor Injection 을 사용하지 못할 때가 있다. Android 컴포넌트(Activity, Fragment 등) 를 사용하는 경우가 그 예다. Fragment 의 생성자는 비워둬야 하는 것이 원칙이고, Activity 의 생성자는 건드릴 방법이 전혀 없기 때문이다.

Field Injection 지원

Dagger 에서는 이러한 상황에서 필드 주입 (Field Injection) 을 사용할 수 있도록 inject() 메소드를 제공해준다. 예를 들어 아래와 같은 ComponentActivity, ViewModel 이 있다고 하자. 필드 주입을 위한 준비 과정은 주제에 벗어나므로 생략한다.

class MyApplication: Application() {
    val appComponent = DaggerApplicationComponent.create()
}

@Component
interface ApplicationComponent {
    fun inject(activity: LoginActivity)
}

class LoginActivity: Activity() {
    @Inject lateinit var loginViewModel: LoginViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }
}

class LoginViewModel @Inject constructor(
    private val userRepository: UserRepository
) { ... }

이 경우 Activity 는 Dagger 에서 제공해주는 다음과 같은 코드를 추가함으로써 필드 주입을 받을 수 있다.

(applicationContext as MyApplication).appComponent.inject(this)

그럼 Field Injection 은 onCreate() 시점에 일어나야겠군!

필드 주입은 클래스가 인스턴스화되고 난 뒤 최대한 빠른 시점에 일어나야 한다. 우선 Activity 의 상황에서는, 우리가 흔히 알고 있는 Lifecycle Callback 인 onCreate() 시점에서 필드 주입이 일어나야 맞다.

따라서 아래와 같이 필드 주입을 실행할 수 있다.

class LoginActivity: Activity() {
    @Inject lateinit var loginViewModel: LoginViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        (applicationContext as MyApplication).appComponent.inject(this)
    }
}

그러나 위와 같이 코드를 작성할 경우 문제가 발생한다. 얼핏 보기에는 잘못된 부분이 없지만, super.onCreate() 에서 일어나는 일에 의거하면 문제가 발생하는 것이 맞다.

Activity 에서 Configuration Change 등이 일어날 때 화면을 구성 및 복구하기 위해 super.onCreate(savedInstanceState) 가 호출되면, 해당 액티비티에 속해있는 Fragment 들을 차례대로 Attach 하게 된다. 그런데 해당 Fragment 들은 자신이 속한 Activity 에 접근할 수 있기 때문에, Fragment 가 Attach 되기 전에 상위 Activity 가 먼저 완성되어 있어야 한다. 즉, DI 가 모두 이루어지고 난 뒤 super.onCreate() 가 호출되어야 한다. 따라서 코드는 다음과 같이 작성되어야 한다.

class LoginActivity: Activity() {
    @Inject lateinit var loginViewModel: LoginViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
	    // 반드시 super.onCreate() 보다 먼저 불려야 한다!
        (applicationContext as MyApplication).appComponent.inject(this)
        super.onCreate(savedInstanceState)
    }
}

추가로, Fragment 에선 어느 시점에 필드 주입이 일어나야 할까?

우리가 알고 있는 Fragment 의 생명주기에 따르면, onAttach() 시점에 필드 주입이 일어나야 맞다. 인스턴스화 이후 최대한 빠르게 필드 주입이 일어나야하기 때문이다. 동작 시간 상 onAttach() 와 그리 차이가 나지 않는 onCreate() 도 고려해볼 수 있겠지만, FragmentonCreate()Fragment 가 Attach 되고 난 뒤에 두 번 다시 호출되지 않는다.

즉, FragmentRe-attach 되는 상황에서 DI 가 정상동작 안 할 수 있기 때문에 onAttach() 시점에 필드 주입이 일어나야 한다.

아래 사진을 보면, Re-attach 상황에서 onCreate() 를 스킵하는 것을 확인할 수 있다.

참고로 Fragment 에서는 Activity 상황과 다르게 화면 복원과 관련하여 더이상 문제될 것이 없기 때문에 super.onAttach() 전후 상관없이 필드 주입을 할 수 있다.


override fun onAttach(context: Context) {
	super.onAttach(context)
	(applicationContext as MyApplication).appComponent.inject(this)
}
profile
어려울수록 기본에 미치고 열광하라

7개의 댓글

comment-user-thumbnail
2022년 12월 12일

오히려 그 초심을 망실하지 않았기에, 헤매지 않고 원점으로 돌아올 수 있었다 생각합니다.
awesome devblog 에 오랜만에 글이 올라와 댓글 달아봅니다.

좋은 글 잘 읽었습니다.

1개의 답글
comment-user-thumbnail
2022년 12월 13일

잘읽고갑니다

1개의 답글
comment-user-thumbnail
2022년 12월 18일

글 기다리고 있었습니다! 저는 티스토리 유저라 Velog글은 없지만 항상 좋은 글 감사합니다

1개의 답글
comment-user-thumbnail
2023년 1월 23일

만 2년차 현업 안드로이드 개발자입니다.
유튜브에서 우연히 이력서 소개 영상을 보아 방문하게 되었는데 정말 깊고 꾸준하게 공부를 잘 해오신 것 같아 존경스럽네요 ㅎㅎ 블로그를 둘러보며 세상엔 정말 열심히 하는 사람들이 많은 걸 다시 한 번 깨달았어요.
종종 들르겠습니다.

좋은 포스팅 감사해요.

답글 달기