<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>안드로이드 개발자 노트</title>
    <link>https://augustin26.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Thu, 16 Apr 2026 23:12:06 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>어리둥절범고래</managingEditor>
    <item>
      <title>[Android] MVI 패턴</title>
      <link>https://augustin26.tistory.com/entry/Android-MVI-%ED%8C%A8%ED%84%B4</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;MVVM은&amp;nbsp;안드로이드에서&amp;nbsp;많이&amp;nbsp;사용되는&amp;nbsp;아키텍처이지만,&amp;nbsp;화면이&amp;nbsp;복잡해질수록&amp;nbsp;상태가&amp;nbsp;여러&amp;nbsp;LiveData나&amp;nbsp;StateFlow로&amp;nbsp;나뉘어&amp;nbsp;관리되는&amp;nbsp;경우가&amp;nbsp;많습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게&amp;nbsp;되면&amp;nbsp;현재&amp;nbsp;화면&amp;nbsp;상태를&amp;nbsp;한눈에&amp;nbsp;파악하기&amp;nbsp;어렵고,&amp;nbsp;상태&amp;nbsp;변경&amp;nbsp;흐름을&amp;nbsp;추적하거나&amp;nbsp;디버깅하는&amp;nbsp;것도&amp;nbsp;복잡해질&amp;nbsp;수&amp;nbsp;있습니다.&lt;br /&gt;&lt;br /&gt;또한&amp;nbsp;DataBinding이나&amp;nbsp;양방향&amp;nbsp;바인딩을&amp;nbsp;함께&amp;nbsp;사용할&amp;nbsp;경우,&amp;nbsp;View와&amp;nbsp;ViewModel&amp;nbsp;사이의&amp;nbsp;상태&amp;nbsp;변경&amp;nbsp;흐름이&amp;nbsp;분산되어&amp;nbsp;유지보수가&amp;nbsp;어려워질&amp;nbsp;수&amp;nbsp;있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;758&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfE9ju/dJMcacJrXoI/rN9xVkAj8kvJVZexuWkxX0/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfE9ju/dJMcacJrXoI/rN9xVkAj8kvJVZexuWkxX0/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfE9ju/dJMcacJrXoI/rN9xVkAj8kvJVZexuWkxX0/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfE9ju%2FdJMcacJrXoI%2FrN9xVkAj8kvJVZexuWkxX0%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;758&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;758&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를&amp;nbsp;들어&amp;nbsp;로그인&amp;nbsp;화면에서&amp;nbsp;ViewModel이&amp;nbsp;여러&amp;nbsp;LiveData로&amp;nbsp;상태를&amp;nbsp;나눠서&amp;nbsp;View와&amp;nbsp;상호작용하는&amp;nbsp;경우는&amp;nbsp;아래처럼&amp;nbsp;볼&amp;nbsp;수&amp;nbsp;있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1775489142684&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class LoginViewModel : ViewModel() {

    private val _email = MutableLiveData(&quot;&quot;)
    val email: LiveData&amp;lt;String&amp;gt; = _email

    private val _password = MutableLiveData(&quot;&quot;)
    val password: LiveData&amp;lt;String&amp;gt; = _password

    private val _isLoading = MutableLiveData(false)
    val isLoading: LiveData&amp;lt;Boolean&amp;gt; = _isLoading

    private val _errorMessage = MutableLiveData&amp;lt;String?&amp;gt;(null)
    val errorMessage: LiveData&amp;lt;String?&amp;gt; = _errorMessage

    private val _loginSuccess = MutableLiveData(false)
    val loginSuccess: LiveData&amp;lt;Boolean&amp;gt; = _loginSuccess

    fun onEmailChanged(value: String) {
        _email.value = value
    }

    fun onPasswordChanged(value: String) {
        _password.value = value
    }

    fun login() {
        val currentEmail = _email.value.orEmpty()
        val currentPassword = _password.value.orEmpty()

        _isLoading.value = true
        _errorMessage.value = null

        if (currentEmail == &quot;test@test.com&quot; &amp;amp;&amp;amp; currentPassword == &quot;1234&quot;) {
            _loginSuccess.value = true
        } else {
            _errorMessage.value = &quot;로그인에 실패했습니다.&quot;
        }

        _isLoading.value = false
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;View에서는&amp;nbsp;각각의&amp;nbsp;LiveData를&amp;nbsp;따로&amp;nbsp;관찰하면서&amp;nbsp;UI를&amp;nbsp;업데이트합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1775489150306&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class LoginActivity : AppCompatActivity() {

    private lateinit var binding: ActivityLoginBinding
    private val viewModel: LoginViewModel by viewModels()

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

        binding.emailEditText.doOnTextChanged { text, _, _, _ -&amp;gt;
            viewModel.onEmailChanged(text.toString())
        }

        binding.passwordEditText.doOnTextChanged { text, _, _, _ -&amp;gt;
            viewModel.onPasswordChanged(text.toString())
        }

        binding.loginButton.setOnClickListener {
            viewModel.login()
        }

        viewModel.isLoading.observe(this) { isLoading -&amp;gt;
            binding.progressBar.isVisible = isLoading
            binding.loginButton.isEnabled = !isLoading
        }

        viewModel.errorMessage.observe(this) { message -&amp;gt;
            binding.errorTextView.text = message.orEmpty()
            binding.errorTextView.isVisible = !message.isNullOrEmpty()
        }

        viewModel.loginSuccess.observe(this) { success -&amp;gt;
            if (success) {
                startActivity(Intent(this, HomeActivity::class.java))
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면, MVI는 상태를 단일 소스로 관리하고 단방향 데이터 흐름을 따르는 것으로 상태 관리의 일관성을 유지하고 디버깅을 용이하게 합니다.&lt;br /&gt;또한, MVI는 Intent를 통해 사용자 행동을 처리합니다. 이는 View와 ViewModel 간의 상호작용을 단일화하여, 코드의 가독성과 유지보수를 향상시킵니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;758&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mG8X0/dJMcaiv4Hpu/VWzjCLaO3uZTCC6E7Pfaj1/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mG8X0/dJMcaiv4Hpu/VWzjCLaO3uZTCC6E7Pfaj1/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mG8X0/dJMcaiv4Hpu/VWzjCLaO3uZTCC6E7Pfaj1/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmG8X0%2FdJMcaiv4Hpu%2FVWzjCLaO3uZTCC6E7Pfaj1%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;758&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;758&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVI는 세 가지 주요 구성 요소로 이루어져 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Intent&lt;/b&gt;: Intent는 사용자의 행동이나 이벤트를 나타냅니다. 버튼 클릭, 스크롤, 또는 데이터 입력 등&lt;/li&gt;
&lt;li&gt;&lt;b&gt;State&lt;/b&gt;: State는 애플리케이션의 현재 상태를 나타냅니다. UI를 렌더링하는 데 필요한 모든 데이터를 포함&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Reducer&lt;/b&gt;: Reducer는 이전 상태와 Intent를 받아 새로운 상태를 생성하는 함수입니다. Reducer는&amp;nbsp;현재&amp;nbsp;상태와&amp;nbsp;전달된&amp;nbsp;Intent를&amp;nbsp;받아&amp;nbsp;새로운&amp;nbsp;상태를&amp;nbsp;생성하는&amp;nbsp;역할을&amp;nbsp;하며,&amp;nbsp;상태&amp;nbsp;변경&amp;nbsp;규칙을&amp;nbsp;한&amp;nbsp;곳에&amp;nbsp;모아&amp;nbsp;관리할&amp;nbsp;수&amp;nbsp;있도록&amp;nbsp;해줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1775489514410&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 사용자의 행동을 Intent로 정의합니다.
sealed interface LoginIntent {
    data class EmailChanged(val value: String) : LoginIntent
    data class PasswordChanged(val value: String) : LoginIntent
    data object LoginClicked : LoginIntent
    data object LoginSucceeded : LoginIntent
    data class LoginFailed(val message: String) : LoginIntent
}

// 화면 상태를 하나의 객체로 정의합니다.
data class LoginUiState(
    val email: String = &quot;&quot;,
    val password: String = &quot;&quot;,
    val isLoading: Boolean = false,
    val errorMessage: String? = null
)

// 한 번만 처리되어야 하는 이벤트를 SideEffect로 정의합니다.
sealed interface LoginSideEffect {
    data object NavigateToHome : LoginSideEffect
    data class ShowToast(val message: String) : LoginSideEffect
}

// Reducer는 이전 상태와 Intent를 받아 새로운 상태를 생성합니다.
fun reducer(
    oldState: LoginState,
    intent: LoginIntent
): LoginState {
    return when (intent) {
        is LoginIntent.EmailChanged -&amp;gt; {
            oldState.copy(
                email = intent.email,
                errorMessage = null
            )
        }

        is LoginIntent.PasswordChanged -&amp;gt; {
            oldState.copy(
                password = intent.password,
                errorMessage = null
            )
        }

        LoginIntent.LoginClicked -&amp;gt; {
            oldState.copy(
                isLoading = true,
                errorMessage = null,
            )
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1775491145623&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class LoginViewModel(
    private val repository: LoginRepository = LoginRepository()
) : ViewModel() {

    private val _uiState = MutableStateFlow(LoginUiState())
    val uiState: StateFlow&amp;lt;LoginUiState&amp;gt; = _uiState.asStateFlow()

    private val _sideEffect = Channel&amp;lt;LoginSideEffect&amp;gt;()
    val sideEffect = _sideEffect.receiveAsFlow()

    fun onIntent(intent: LoginIntent) {
        when (intent) {
            is LoginIntent.EmailChanged,
            is LoginIntent.PasswordChanged -&amp;gt; {
                _uiState.value = reducer(_uiState.value, intent)
            }

            LoginIntent.LoginClicked -&amp;gt; {
                login()
            }
        }
    }

    private fun login() {
        val currentState = _uiState.value
        if (currentState.email.isBlank()) {
            viewModelScope.launch {
                _sideEffect.send(LoginSideEffect.ShowToast(&quot;이메일을 입력해주세요.&quot;))
            }
            return
        }
        if (currentState.password.isBlank()) {
            viewModelScope.launch {
                _sideEffect.send(LoginSideEffect.ShowToast(&quot;비밀번호를 입력해주세요.&quot;))
            }
            return
        }
        _uiState.value = reducer(_uiState.value, LoginIntent.LoginClicked)
        val snapshot = _uiState.value
        viewModelScope.launch {
            repository.login(
                email = snapshot.email,
                password = snapshot.password
            ).onSuccess {
                _uiState.value = reducer(_uiState.value, LoginIntent.LoginSucceeded)
                _sideEffect.send(LoginSideEffect.NavigateToHome)
            }.onFailure { throwable -&amp;gt;
                val message = throwable.message ?: &quot;로그인에 실패했습니다.&quot;
                _uiState.value = reducer(_uiState.value, LoginIntent.LoginFailed(message))
            _sideEffect.send(LoginSideEffect.ShowToast(message))
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1775491149925&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun LoginScreen(
    viewModel: LoginViewModel,
    onNavigateToHome: () -&amp;gt; Unit
) {
    val uiState by viewModel.uiState.collectAsState()
    val context = LocalContext.current

    LaunchedEffect(Unit) {
        viewModel.sideEffect.collect { effect -&amp;gt;
            when (effect) {
                LoginSideEffect.NavigateToHome -&amp;gt; onNavigateToHome()
                is LoginSideEffect.ShowToast -&amp;gt; {
                    Toast.makeText(context, effect.message, Toast.LENGTH_SHORT).show()
                }
            }
        }
    }

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(24.dp),
        verticalArrangement = Arrangement.spacedBy(12.dp)
    ) {
        OutlinedTextField(
            value = uiState.email,
            onValueChange = { viewModel.onIntent(LoginIntent.EmailChanged(it)) },
            label = { Text(&quot;이메일&quot;) },
            modifier = Modifier.fillMaxWidth(),
            enabled = !uiState.isLoading
        )

        OutlinedTextField(
            value = uiState.password,
            onValueChange = { viewModel.onIntent(LoginIntent.PasswordChanged(it)) },
            label = { Text(&quot;비밀번호&quot;) },
            visualTransformation = PasswordVisualTransformation(),
            modifier = Modifier.fillMaxWidth(),
            enabled = !uiState.isLoading
        )

        uiState.errorMessage?.let { message -&amp;gt;
            Text(
                text = message,
                color = MaterialTheme.colorScheme.error
            )
        }

        Button(
            onClick = { viewModel.onIntent(LoginIntent.LoginClicked) },
            modifier = Modifier.fillMaxWidth(),
            enabled = !uiState.isLoading
        ) {
            if (uiState.isLoading) {
                CircularProgressIndicator()
            } else {
                Text(&quot;로그인&quot;)
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 내용만 보면 Compose에서 MVI를 사용하지 않을 이유가 없어 보이지만 몇 가지 단점도 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;보일러플레이트&amp;nbsp;증가&lt;/b&gt;:&amp;nbsp;화면마다&amp;nbsp;State,&amp;nbsp;Intent,&amp;nbsp;SideEffect&amp;nbsp;등을&amp;nbsp;별도로&amp;nbsp;정의해야&amp;nbsp;해&amp;nbsp;코드량이&amp;nbsp;늘어난다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;복잡성&amp;nbsp;증가&lt;/b&gt;:&amp;nbsp;단순한&amp;nbsp;화면이나&amp;nbsp;작은&amp;nbsp;앱에서는&amp;nbsp;구조가&amp;nbsp;과할&amp;nbsp;수&amp;nbsp;있고,&amp;nbsp;불필요한&amp;nbsp;복잡성을&amp;nbsp;만들&amp;nbsp;수&amp;nbsp;있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;성능&amp;nbsp;오버헤드&amp;nbsp;가능성&lt;/b&gt;:&amp;nbsp;상태를&amp;nbsp;불변&amp;nbsp;객체로&amp;nbsp;관리하기&amp;nbsp;때문에&amp;nbsp;변경&amp;nbsp;시마다&amp;nbsp;새로운&amp;nbsp;객체가&amp;nbsp;생성된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;러닝&amp;nbsp;커브&lt;/b&gt;:&amp;nbsp;개념이&amp;nbsp;많아&amp;nbsp;처음에는&amp;nbsp;진입장벽이&amp;nbsp;있을&amp;nbsp;수&amp;nbsp;있다.&amp;nbsp;다만&amp;nbsp;개인적으로는&amp;nbsp;라이브러리를&amp;nbsp;활용하면&amp;nbsp;MVVM보다&amp;nbsp;더&amp;nbsp;빠르게&amp;nbsp;적응할&amp;nbsp;수&amp;nbsp;있었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Orbit의&amp;nbsp;등장&amp;nbsp;배경&lt;br /&gt;&lt;br /&gt;MVI는 상태 변화 흐름을 명확하게 관리할 수 있다는 장점이 있지만, 직접 구현하려고 하면 State, SideEffect, Reducer, Flow, Channel 등을 반복해서 작성해야 해 보일러플레이트가 많아집니다. 또한 화면마다 비슷한 패턴을 계속 구현해야 하므로 코드 일관성을 유지하기도 쉽지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 문제를 줄이기 위해 등장한 것이 Orbit으로, MVI 패턴을 Android 환경에서 보다 간결하게 사용할 수 있도록 도와주는 라이브러리이며 상태 관리와 사이드 이펙트 처리를 일관된 방식으로 구현할 수 있게 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Orbit의&amp;nbsp;핵심&amp;nbsp;키워드&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Container&lt;/b&gt;: Orbit에서 상태와 사이드 이펙트를 보관하고 관리하는 중심 객체이다. 현재 State와 SideEffect의 흐름을 하나의 단위로 관리한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ContainerHost&lt;/b&gt;: ViewModel이 Orbit의 상태 컨테이너를 가지도록 해주는 인터페이스이다. Orbit을 사용하는 ViewModel은 보통 ContainerHost&amp;lt;State, SideEffect&amp;gt;를 구현한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;State&lt;/b&gt;: 화면을 그리는 데 필요한 현재 상태이다. Compose에서는 이 State를 구독하여 UI를 렌더링한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SideEffect&lt;/b&gt;: 한 번만 처리되어야 하는 이벤트이다. 예를 들어 화면 이동, Toast 표시, Snackbar 출력 등이 여기에 해당한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Intent&lt;/b&gt;: 사용자의 입력이나 이벤트를 처리하는 블록이다. Orbit에서는 intent { ... } 형태로 상태 변경이나 비즈니스 로직을 수행한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Reduce&lt;/b&gt;: 현재 상태를 새로운 상태로 변경하는 역할을 한다. Orbit에서는 reduce { state.copy(...) } 형태로 불변 상태를 갱신한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;postSideEffect&lt;/b&gt;: 일회성 이벤트를 방출할 때 사용하는 함수이다. 상태와 분리해서 처리해야 하는 UI 이벤트를 전달할 때 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1775493205788&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import androidx.lifecycle.ViewModel
import org.orbitmvi.orbit.ContainerHost
import org.orbitmvi.orbit.viewmodel.container
import org.orbitmvi.orbit.syntax.simple.intent
import org.orbitmvi.orbit.syntax.simple.reduce
import org.orbitmvi.orbit.syntax.simple.postSideEffect

class LoginViewModel(
    private val repository: LoginRepository = LoginRepository()
) : ViewModel(), ContainerHost&amp;lt;LoginUiState, LoginSideEffect&amp;gt; {

    override val container = container&amp;lt;LoginUiState, LoginSideEffect&amp;gt;(LoginUiState())

    fun onEmailChanged(value: String) = intent {
        reduce {
            state.copy(
                email = value,
                errorMessage = null
            )
        }
    }

    fun onPasswordChanged(value: String) = intent {
        reduce {
            state.copy(
                password = value,
                errorMessage = null
            )
        }
    }

    fun onLoginClicked() = intent {
        if (state.email.isBlank()) {
            postSideEffect(LoginSideEffect.ShowToast(&quot;이메일을 입력해주세요.&quot;))
            return@intent
        }

        if (state.password.isBlank()) {
            postSideEffect(LoginSideEffect.ShowToast(&quot;비밀번호를 입력해주세요.&quot;))
            return@intent
        }

        reduce {
            state.copy(
                isLoading = true,
                errorMessage = null
            )
        }

        repository.login(
            email = state.email,
            password = state.password
        ).onSuccess {
            reduce {
                state.copy(
                    isLoading = false,
                    errorMessage = null
                )
            }
            postSideEffect(LoginSideEffect.NavigateToHome)
        }.onFailure { throwable -&amp;gt;
            val message = throwable.message ?: &quot;로그인에 실패했습니다.&quot;

            reduce {
                state.copy(
                    isLoading = false,
                    errorMessage = message
                )
            }
            postSideEffect(LoginSideEffect.ShowToast(message))
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1775493214437&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import android.widget.Toast
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import org.orbitmvi.orbit.compose.collectAsState
import org.orbitmvi.orbit.compose.collectSideEffect

@Composable
fun LoginScreen(
    viewModel: LoginViewModel,
    onNavigateToHome: () -&amp;gt; Unit
) {
    val uiState by viewModel.collectAsState()
    val context = LocalContext.current

    viewModel.collectSideEffect { effect -&amp;gt;
        when (effect) {
            LoginSideEffect.NavigateToHome -&amp;gt; onNavigateToHome()
            is LoginSideEffect.ShowToast -&amp;gt; {
                Toast.makeText(context, effect.message, Toast.LENGTH_SHORT).show()
            }
        }
    }

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(24.dp),
        verticalArrangement = Arrangement.spacedBy(12.dp)
    ) {
        OutlinedTextField(
            value = uiState.email,
            onValueChange = viewModel::onEmailChanged,
            label = { Text(&quot;이메일&quot;) },
            modifier = Modifier.fillMaxWidth(),
            enabled = !uiState.isLoading
        )

        OutlinedTextField(
            value = uiState.password,
            onValueChange = viewModel::onPasswordChanged,
            label = { Text(&quot;비밀번호&quot;) },
            visualTransformation = PasswordVisualTransformation(),
            modifier = Modifier.fillMaxWidth(),
            enabled = !uiState.isLoading
        )

        uiState.errorMessage?.let { message -&amp;gt;
            Text(
                text = message,
                color = MaterialTheme.colorScheme.error
            )
        }

        Button(
            onClick = viewModel::onLoginClicked,
            modifier = Modifier.fillMaxWidth(),
            enabled = !uiState.isLoading
        ) {
            if (uiState.isLoading) {
                CircularProgressIndicator()
            } else {
                Text(&quot;로그인&quot;)
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Android</category>
      <author>어리둥절범고래</author>
      <guid isPermaLink="true">https://augustin26.tistory.com/142</guid>
      <comments>https://augustin26.tistory.com/entry/Android-MVI-%ED%8C%A8%ED%84%B4#entry142comment</comments>
      <pubDate>Tue, 7 Apr 2026 01:32:11 +0900</pubDate>
    </item>
    <item>
      <title>멀티 모듈 앱에서 Dagger 사용</title>
      <link>https://augustin26.tistory.com/entry/%EB%A9%80%ED%8B%B0-%EB%AA%A8%EB%93%88-%EC%95%B1%EC%97%90%EC%84%9C-Dagger-%EC%82%AC%EC%9A%A9</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;멀티 모듈 프로젝트에서는 보통 app 모듈이 Application을 가지고 있고, core나 base 모듈에 공통 코드가 들어갑니다. app 모듈은 앱 전역에서 공유되는 객체를 제공하는 컴포넌트를 두기에 적절합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1114&quot; data-origin-height=&quot;764&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GLVnE/dJMcahjaAaG/G0kfRom7dXWsiUE4NbKY01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GLVnE/dJMcahjaAaG/G0kfRom7dXWsiUE4NbKY01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GLVnE/dJMcahjaAaG/G0kfRom7dXWsiUE4NbKY01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGLVnE%2FdJMcahjaAaG%2FG0kfRom7dXWsiUE4NbKY01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1114&quot; height=&quot;764&quot; data-origin-width=&quot;1114&quot; data-origin-height=&quot;764&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;구조 잡는 기준&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;멀티 모듈에서 같은 레벨의 모듈끼리는 서로 의존하지 않는 것이 일반적인 규칙입니다. 같은 레벨에서 의존이 생기면 공통 로직을 상위 모듈로 올리거나, 상위 모듈을 확장하는 새 모듈을 만들고 두 모듈이 그 모듈에 의존하도록 리팩터링하는 방식을 권장합니다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;컴포넌트를&amp;nbsp;모듈에&amp;nbsp;만드는&amp;nbsp;경우는&amp;nbsp;보통&amp;nbsp;두&amp;nbsp;가지입니다.&amp;nbsp;필드&amp;nbsp;주입이&amp;nbsp;필요하거나,&amp;nbsp;특정&amp;nbsp;흐름에서&amp;nbsp;객체를&amp;nbsp;스코프&amp;nbsp;처리해야&amp;nbsp;할&amp;nbsp;때입니다.&amp;nbsp;둘&amp;nbsp;다&amp;nbsp;아니라면&amp;nbsp;컴포넌트&amp;nbsp;대신&amp;nbsp;@Provides나&amp;nbsp;@Binds를&amp;nbsp;가진&amp;nbsp;Dagger&amp;nbsp;모듈을&amp;nbsp;노출하는&amp;nbsp;방식이&amp;nbsp;더&amp;nbsp;단순합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;멀티 모듈에서 Subcomponent를 그대로 쓰기 어려운 이유&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;단일&amp;nbsp;모듈&amp;nbsp;예제에서는&amp;nbsp;Activity에서&amp;nbsp;MyApplication의&amp;nbsp;appComponent를&amp;nbsp;가져와서&amp;nbsp;서브컴포넌트를&amp;nbsp;만드는&amp;nbsp;방식이&amp;nbsp;자연스럽습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1771738629959&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class LoginActivity: Activity() {
  ...

  override fun onCreate(savedInstanceState: Bundle?) {
    // Application 그래프에서 로그인 그래프를 생성
    loginComponent = (applicationContext as MyDaggerApplication)
                        .appComponent.loginComponent().create()

    // LoginActivity의 @Inject 필드를 주입
    loginComponent.inject(this)
    ...
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;하지만 login 같은 기능 모듈에서는 MyApplication이나 appComponent가 있는 모듈을 직접 알지 못할 수 있어서 이 코드가 그대로는 동작하지 않습니다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이&amp;nbsp;경우&amp;nbsp;기능&amp;nbsp;모듈&amp;nbsp;안에&amp;nbsp;컴포넌트를&amp;nbsp;제공하는&amp;nbsp;인터페이스를&amp;nbsp;하나&amp;nbsp;두고,&amp;nbsp;Application이&amp;nbsp;그&amp;nbsp;인터페이스를&amp;nbsp;구현하는&amp;nbsp;형태로&amp;nbsp;풀&amp;nbsp;수&amp;nbsp;있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1771738641497&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface LoginComponentProvider {
    fun provideLoginComponent(): LoginComponent
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;LoginActivity는 Application을 이 인터페이스로 캐스팅해서 컴포넌트를 받습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1771738649009&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class LoginActivity: Activity() {
  ...

  override fun onCreate(savedInstanceState: Bundle?) {
    loginComponent = (applicationContext as LoginComponentProvider)
                        .provideLoginComponent()

    loginComponent.inject(this)
    ...
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Application은 실제로 appComponent에서 LoginComponent를 만들어 반환합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1771738654828&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class MyApplication: Application(), LoginComponentProvider {
  val appComponent = DaggerApplicationComponent.create()

  override fun provideLoginComponent(): LoginComponent {
    return appComponent.loginComponent().create()
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이 방식은 일반적인 멀티 모듈 프로젝트에서 서브컴포넌트를 사용하는 방법으로 소개됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;feature&amp;nbsp;module에서는&amp;nbsp;component&amp;nbsp;dependencies를&amp;nbsp;사용&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;feature module이 들어가면 모듈 의존 방향이 반대로 되는 경우가 많습니다. app 모듈이 feature module을 포함하는 구조가 아니라, feature module이 app 모듈에 의존하는 구조가 됩니다. 이 구조에서는 app 모듈이 feature module 안에 정의된 서브컴포넌트를 빌드 경로에서 볼 수 없어서, 부모 컴포넌트의 서브컴포넌트로 연결하는 방식이 깨집니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이&amp;nbsp;문제를&amp;nbsp;해결하기&amp;nbsp;위해&amp;nbsp;Dagger의&amp;nbsp;component&amp;nbsp;dependencies를&amp;nbsp;사용합니다.&amp;nbsp;서브컴포넌트처럼&amp;nbsp;부모&amp;nbsp;자식&amp;nbsp;관계를&amp;nbsp;만드는&amp;nbsp;대신,&amp;nbsp;자식&amp;nbsp;컴포넌트가&amp;nbsp;부모&amp;nbsp;컴포넌트에&amp;nbsp;의존하도록&amp;nbsp;만들고,&amp;nbsp;부모가&amp;nbsp;그래프에서&amp;nbsp;제공하는&amp;nbsp;타입을&amp;nbsp;자식이&amp;nbsp;소비하는&amp;nbsp;방식입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;847&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VcFW3/dJMcafMpW6V/FvutfKWqAlPKM2Q6z3c3l1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VcFW3/dJMcafMpW6V/FvutfKWqAlPKM2Q6z3c3l1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VcFW3/dJMcafMpW6V/FvutfKWqAlPKM2Q6z3c3l1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVcFW3%2FdJMcafMpW6V%2FFvutfKWqAlPKM2Q6z3c3l1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;847&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;847&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;component dependencies 예제&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;app 모듈에 UserRepository가 있고 AppComponent에 스코프되어 있다고 가정합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1771738835714&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class UserLocalDataSource @Inject constructor() { ... }
class UserRemoteDataSource @Inject constructor() { ... }

@Singleton
class UserRepository @Inject constructor(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) { ... }

@Singleton
@Component
interface AppComponent { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;login 모듈의 LoginViewModel은 UserRepository에 의존합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1771738843559&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class LoginViewModel @Inject constructor(
    private val userRepository: UserRepository
) { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이때&amp;nbsp;LoginActivity를&amp;nbsp;주입할&amp;nbsp;LoginComponent를&amp;nbsp;만들되,&amp;nbsp;AppComponent에&amp;nbsp;의존하도록&amp;nbsp;선언합니다.&amp;nbsp;서브컴포넌트가&amp;nbsp;아니라&amp;nbsp;@Component로&amp;nbsp;선언하는&amp;nbsp;것이&amp;nbsp;포인트입니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1771738850330&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component(dependencies = [AppComponent::class])
interface LoginComponent {
    fun inject(activity: LoginActivity)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;의존 컴포넌트는 생성할 때 부모 컴포넌트 인스턴스를 넘겨야 하므로 Factory를 둡니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1771738856624&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component(dependencies = [AppComponent::class])
interface LoginComponent {

    @Component.Factory
    interface Factory {
        fun create(appComponent: AppComponent): LoginComponent
    }

    fun inject(activity: LoginActivity)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;LoginActivity에서는 MyApplication에서 appComponent를 가져온 뒤, LoginComponent를 생성하고 inject를 호출합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1771738864352&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class LoginActivity: Activity() {

    @Inject lateinit var loginViewModel: LoginViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        val appComponent = (applicationContext as MyApplication).appComponent

        DaggerLoginComponent.factory().create(appComponent).inject(this)

        super.onCreate(savedInstanceState)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;의존 컴포넌트에서의 스코프 규칙&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;의존 관계로 연결된 두 컴포넌트는 같은 스코프 애노테이션을 공유할 수 없습니다. LoginComponent가 AppComponent 인스턴스를 사용하기 때문입니다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;LoginViewModel을&amp;nbsp;LoginComponent&amp;nbsp;범위로&amp;nbsp;묶고&amp;nbsp;싶다면&amp;nbsp;이전과&amp;nbsp;동일하게&amp;nbsp;커스텀&amp;nbsp;스코프를&amp;nbsp;사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1771738889460&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@ActivityScope
@Component(dependencies = [AppComponent::class])
interface LoginComponent { ... }

@ActivityScope
class LoginViewModel @Inject constructor(
    private val userRepository: UserRepository
) { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;또한 의존 컴포넌트가 필요로 하는 타입을 부모 컴포넌트가 충분히 노출하지 않으면, Dagger가 컴파일 타임에 에러를 냅니다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;정리&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;ApplicationComponent는 항상 app 모듈에 둔다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;필드 주입이 필요하거나 특정 흐름에서 스코프가 필요할 때 해당 모듈에 컴포넌트를 둔다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;유틸리티 성격의 Gradle 모듈처럼 그래프를 만들 필요가 없다면, 컴포넌트 대신 @Provides나 @Binds를 가진 Dagger 모듈을 공개한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;feature module이 있는 앱에서 ApplicationComponent의 의존성을 사용해야 한다면 component dependencies를 사용한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Android</category>
      <author>어리둥절범고래</author>
      <guid isPermaLink="true">https://augustin26.tistory.com/141</guid>
      <comments>https://augustin26.tistory.com/entry/%EB%A9%80%ED%8B%B0-%EB%AA%A8%EB%93%88-%EC%95%B1%EC%97%90%EC%84%9C-Dagger-%EC%82%AC%EC%9A%A9#entry141comment</comments>
      <pubDate>Tue, 24 Feb 2026 09:02:28 +0900</pubDate>
    </item>
    <item>
      <title>Android 앱에서 Dagger 사용</title>
      <link>https://augustin26.tistory.com/entry/Android-%EC%95%B1%EC%97%90%EC%84%9C-Dagger-%EC%82%AC%EC%9A%A9</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Android에서 Dagger를 적용할 때는 Application 수명주기에 맞는 그래프를 만들고, Activity와 Fragment에는 필드 주입으로 연결하는 방식이 기본입니다. 그래프는 Dagger가 관리하는 객체 관계라고 보면 됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가능하면 생성자 주입을 사용&lt;/li&gt;
&lt;li&gt;생성자 주입이 어려운 타입은 모듈로 제공&lt;/li&gt;
&lt;li&gt;모듈은 컴포넌트에서 한 번만 선언하는 구조로 작성&lt;/li&gt;
&lt;li&gt;스코프는 기능 이름보다 수명주기 기준으로 작성&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Android의&amp;nbsp;Dagger&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;549&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwTgmN/dJMcabpIu88/RLARVTvRfXuInEmcOPKOd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwTgmN/dJMcabpIu88/RLARVTvRfXuInEmcOPKOd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwTgmN/dJMcabpIu88/RLARVTvRfXuInEmcOPKOd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwTgmN%2FdJMcabpIu88%2FRLARVTvRfXuInEmcOPKOd0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;549&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;549&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Application에 그래프 두기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Application에&amp;nbsp;컴포넌트를&amp;nbsp;만들어&amp;nbsp;두면&amp;nbsp;앱&amp;nbsp;실행&amp;nbsp;동안&amp;nbsp;그래프를&amp;nbsp;공유할&amp;nbsp;수&amp;nbsp;있습니다.&amp;nbsp;앱&amp;nbsp;전역에서&amp;nbsp;재사용할&amp;nbsp;객체는&amp;nbsp;이&amp;nbsp;그래프에&amp;nbsp;두는&amp;nbsp;것이&amp;nbsp;자연스럽습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1771734799428&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Application 그래프 정의
@Component
interface ApplicationComponent { ... }

// appComponent는 Application 클래스에 두어서 앱 수명주기와 함께 공유합니다.
class MyApplication: Application() {
    // 앱 전체에서 사용하는 Application 그래프 참조
    val appComponent = DaggerApplicationComponent.create()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;필드 주입&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Activity와&amp;nbsp;Fragment는&amp;nbsp;시스템이&amp;nbsp;인스턴스를&amp;nbsp;만들기&amp;nbsp;때문에&amp;nbsp;생성자&amp;nbsp;주입이&amp;nbsp;어렵습니다.&amp;nbsp;이&amp;nbsp;경우&amp;nbsp;필드에&amp;nbsp;@Inject를&amp;nbsp;붙이고&amp;nbsp;수명주기&amp;nbsp;메서드에서&amp;nbsp;주입을&amp;nbsp;호출합니다.&amp;nbsp;Activity는&amp;nbsp;프래그먼트&amp;nbsp;복원&amp;nbsp;과정에서&amp;nbsp;문제가&amp;nbsp;생길&amp;nbsp;수&amp;nbsp;있으므로&amp;nbsp;super.onCreate()를&amp;nbsp;호출하기&amp;nbsp;전에&amp;nbsp;주입하는&amp;nbsp;흐름으로&amp;nbsp;안내합니다.&amp;nbsp;Fragment는&amp;nbsp;onAttach()에서&amp;nbsp;주입하며,&amp;nbsp;super.onAttach()&amp;nbsp;호출&amp;nbsp;전후&amp;nbsp;어느&amp;nbsp;쪽이든&amp;nbsp;가능합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1771734845410&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class LoginActivity: Activity() {
    // 그래프에서 LoginViewModel 인스턴스를 제공받고 싶습니다.
    @Inject lateinit var loginViewModel: LoginViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        // LoginActivity의 @Inject 필드를 Dagger가 주입하도록 호출합니다.
        (applicationContext as MyApplication).appComponent.inject(this)
        // 이제 loginViewModel을 사용할 수 있습니다.
        super.onCreate(savedInstanceState)
    }
}

// @Inject는 Dagger에게 LoginViewModel 인스턴스를 어떻게 생성할지 알려줍니다.
class LoginViewModel @Inject constructor(
    private val userRepository: UserRepository
) { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;&lt;b&gt;의존성은 생성자 주입으로 이어 붙이기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로 필요한 객체를 만들기 위해 Dagger는 필요한 의존성을 연쇄적으로 생성합니다. 프로젝트 내부 클래스라면 생성자에 @Inject를 붙이는 방식이 가장 단순합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1771734886362&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class UserRepository @Inject constructor(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) { ... }

class UserLocalDataSource @Inject constructor() { ... }

class UserRemoteDataSource @Inject constructor(
    private val loginService: LoginRetrofitService
) { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;&lt;b&gt;Dagger 모듈&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Retrofit처럼 빌더를 통해 만들어야 하는 타입은 생성자 주입으로 만들기 어렵습니다. 이때 모듈의 @Provides로 생성 방법을 제공합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1771734903907&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// @Module은 이 클래스가 Dagger Module임을 알려줍니다.
@Module
class NetworkModule {
    // @Provides는 이 함수가 반환하는 타입을 Dagger가 어떻게 생성해야 하는지 알려줍니다.
    @Provides
    fun provideLoginRetrofitService(): LoginRetrofitService {
        // Dagger가 LoginRetrofitService 인스턴스를 제공해야 할 때마다 실행됩니다.
        return Retrofit.Builder()
                .baseUrl(&quot;https://example.com&quot;)
                .build()
                .create(LoginService::class.java)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Provides 메서드의 파라미터는 그 타입을 만들기 위한 의존성입니다. Dagger가 먼저 파라미터를 준비한 뒤 메서드를 호출합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1771734924502&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Module
class NetworkModule {
    // LoginRetrofitService가 OkHttpClient에 의존한다고 가정한 예시
    @Provides
    fun provideLoginRetrofitService(
        okHttpClient: OkHttpClient
    ): LoginRetrofitService { ... }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모듈은&amp;nbsp;컴포넌트에&amp;nbsp;포함시켜야&amp;nbsp;그래프에서&amp;nbsp;사용됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1771734933703&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// @Component의 modules 속성은 그래프를 만들 때 포함할 Module을 Dagger에 알려줍니다.
@Component(modules = [NetworkModule::class])
interface ApplicationComponent {
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;610&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sCJIc/dJMcahDqbe0/miY8W9ZPIRajIzsJAFGeH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sCJIc/dJMcahDqbe0/miY8W9ZPIRajIzsJAFGeH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sCJIc/dJMcahDqbe0/miY8W9ZPIRajIzsJAFGeH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsCJIc%2FdJMcahDqbe0%2FmiY8W9ZPIRajIzsJAFGeH1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;610&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;610&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Dagger Scope&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스코프는 같은 그래프 안에서 같은 인스턴스를 재사용하기 위한 장치입니다. 앱 전역에서 하나만 유지하고 싶은 객체에는 @Singleton을 사용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1771734976459&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Singleton
@Component(modules = [NetworkModule::class])
interface ApplicationComponent {
    fun inject(activity: LoginActivity)
}

@Singleton
class UserRepository @Inject constructor(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) { ... }

@Module
class NetworkModule {
    // Module 내부에서 제공하는 타입에도 스코프를 부여할 수 있습니다.
    @Singleton
    @Provides
    fun provideLoginRetrofitService(): LoginRetrofitService { ... }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;&lt;b&gt;로그인 흐름은 별도의 그래프로&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인 화면과 여러 Fragment가 하나의 흐름으로 움직이면, 그 흐름 안에서는 같은 LoginViewModel을 공유하는 편이 자연스럽습니다. 하지만 앱 전체 싱글턴으로 두면 흐름이 끝나도 인스턴스가 남을 수 있습니다. 이 경우 Activity 수명주기에 맞는 하위 그래프를 둡니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Subcomponent 구성&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 Login 전용 컴포넌트를 따로 두는 형태로 시작할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1771736633808&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
interface LoginComponent {}
@Component
interface LoginComponent {
    fun inject(activity: LoginActivity)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로는 @Subcomponent를 사용해서 부모 그래프 아래에 로그인 그래프를 두는 형태가 됩니다. Factory를 노출해서 새로운 로그인 그래프 인스턴스를 만들 수 있게 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1771736645782&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Subcomponent
interface LoginComponent {

    // 이 서브컴포넌트 인스턴스를 생성하기 위한 Factory
    @Subcomponent.Factory
    interface Factory {
        fun create(): LoginComponent
    }

    fun inject(loginActivity: LoginActivity)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모 컴포넌트가 자식 서브컴포넌트를 알 수 있도록 모듈에 등록합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1771736656456&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Module의 subcomponents 속성은 자식 Subcomponent를 Dagger에 알려줍니다.
@Module(subcomponents = LoginComponent::class)
class SubcomponentsModule {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모 컴포넌트는 이 모듈을 포함하고 Factory를 밖으로 노출합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1771736666358&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Singleton
@Component(modules = [NetworkModule::class, SubcomponentsModule::class])
interface ApplicationComponent {
}
@Singleton
@Component(modules = [NetworkModule::class, SubcomponentsModule::class])
interface ApplicationComponent {
    // LoginComponent.Factory를 노출합니다.
    fun loginComponent(): LoginComponent.Factory
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;LoginActivity에서 로그인 그래프 생성과 주입&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인 그래프의 수명은 Activity가 책임집니다. Activity가 생성될 때 그래프를 만들고, 사라질 때 함께 사라지는 형태입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1771736692461&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class LoginActivity: Activity() {
    // 로그인 그래프에 대한 참조
    lateinit var loginComponent: LoginComponent
    ...
}
class LoginActivity: Activity() {
    // 로그인 그래프에 대한 참조
    lateinit var loginComponent: LoginComponent

    // 로그인 그래프에서 주입되어야 하는 필드
    @Inject lateinit var loginViewModel: LoginViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        // Application 그래프를 이용해서 로그인 그래프를 생성합니다.
        loginComponent = (applicationContext as MyDaggerApplication)
                            .appComponent.loginComponent().create()

        // LoginActivity의 @Inject 필드를 Dagger가 주입하도록 호출합니다.
        loginComponent.inject(this)

        super.onCreate(savedInstanceState)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;ActivityScope 스코프&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인 그래프 안에서 LoginViewModel을 공유하려면 컴포넌트와 클래스에 같은 스코프를 붙입니다. 그러면 같은 그래프 안에서 동일 인스턴스가 제공됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1771736714294&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ActivityScope라는 커스텀 스코프 정의
@Scope
@Retention(value = AnnotationRetention.RUNTIME)
annotation class ActivityScope

@ActivityScope
@Subcomponent
interface LoginComponent { ... }

@ActivityScope
class LoginViewModel @Inject constructor(
    private val userRepository: UserRepository
) { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Fragment에서도 같은 ViewModel 공유&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fragment도 로그인 그래프에서 주입받아야 같은 LoginViewModel 인스턴스를 공유할 수 있습니다. 그래서 LoginComponent에 Fragment용 inject를 추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1771736736906&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@ActivityScope
@Subcomponent
interface LoginComponent {

    @Subcomponent.Factory
    interface Factory {
        fun create(): LoginComponent
    }

    fun inject(loginActivity: LoginActivity)
    fun inject(usernameFragment: LoginUsernameFragment)
    fun inject(passwordFragment: LoginPasswordFragment)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fragment는&amp;nbsp;onAttach에서&amp;nbsp;주입하는&amp;nbsp;흐름을&amp;nbsp;사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1771736755200&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class LoginUsernameFragment: Fragment() {

    // 로그인 그래프에서 주입되어야 하는 필드
    @Inject lateinit var loginViewModel: LoginViewModel

    override fun onAttach(context: Context) {
        super.onAttach(context)

        // LoginActivity에서 로그인 그래프를 가져와 주입합니다.
        (activity as LoginActivity).loginComponent.inject(this)
    }
}
class LoginPasswordFragment: Fragment() {

    // 로그인 그래프에서 주입되어야 하는 필드
    @Inject lateinit var loginViewModel: LoginViewModel

    override fun onAttach(context: Context) {
        super.onAttach(context)

        (activity as LoginActivity).loginComponent.inject(this)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;그래프&amp;nbsp;구성&amp;nbsp;시&amp;nbsp;권장사항&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트를 만들 때 수명을 누가 책임지는지 먼저 정합니다. Application이 ApplicationComponent를 책임지고, Activity가 LoginComponent를 책임지는 구조가 됩니다. 스코프는 필요한 경우에만 사용합니다. scoped 객체는 내부적으로 관리 비용이 생길 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;&lt;b&gt;테스트&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단위 테스트는 Dagger 없이도 진행할 수 있습니다. 생성자로 fake나 mock을 직접 넣으면 됩니다&lt;/p&gt;
&lt;pre id=&quot;code_1771736862860&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@ActivityScope
class LoginViewModel @Inject constructor(
    private val userRepository: UserRepository
) { ... }

class LoginViewModelTest {

    @Test
    fun `Happy path`() {
        val viewModel = LoginViewModel(fakeUserRepository)
        assertEquals(...)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;통합 테스트는 실제 모듈 대신 테스트 모듈을 끼워서 그래프를 교체합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1771736869863&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Singleton
@Component(modules = [FakeNetworkModule::class, SubcomponentsModule::class])
interface TestApplicationComponent : ApplicationComponent {
}
@Module
class FakeNetworkModule {
    @Provides
    fun provideLoginRetrofitService(): LoginRetrofitService {
        return FakeLoginService()
    }
}
class MyTestApplication: MyApplication() {
    override val appComponent = DaggerTestApplicationComponent.create()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Dagger 모듈 중복 include 주의&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모듈을 include로 조합하다 보면 같은 모듈이 그래프에 여러 번 포함될 수 있습니다. 문서에서는 이를 피하는 구조를 예시로 설명합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1771736909820&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component(modules = [Module1::class, Module2::class])
interface ApplicationComponent { ... }

@Module(includes = [ModuleX::class])
class Module1 { ... }

@Module
class Module2 { ... }
@Component(modules = [Module1::class, Module2::class])
interface ApplicationComponent { ... }

@Module(includes = [ModuleX::class])
class Module1 { ... }

@Module(includes = [ModuleX::class])
class Module2 { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 가지 방법은 공통 모듈을 컴포넌트로 올리는 것입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1771736920781&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component(modules = [Module1::class, Module2::class, ModuleX::class])
interface ApplicationComponent { ... }

@Module
class Module1 { ... }

@Module
class Module2 { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른&amp;nbsp;방법은&amp;nbsp;공통&amp;nbsp;부분과&amp;nbsp;모듈별&amp;nbsp;의존&amp;nbsp;부분을&amp;nbsp;분리하는&amp;nbsp;것입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1771736930449&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component(modules = [Module1::class, Module2::class, ModuleXCommon::class])
interface ApplicationComponent { ... }

@Module
class ModuleXCommon { ... }

@Module
class ModuleXWithModule1SpecificDependencies { ... }

@Module
class ModuleXWithModule2SpecificDependencies { ... }

@Module(includes = [ModuleXWithModule1SpecificDependencies::class])
class Module1 { ... }

@Module(includes = [ModuleXWithModule2SpecificDependencies::class])
class Module2 { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Application에 ApplicationComponent를 두고 앱 수명주기와 함께 사용&lt;/li&gt;
&lt;li&gt;Activity와 Fragment는 필드 주입으로 연결&lt;/li&gt;
&lt;li&gt;외부 라이브러리 객체는 모듈에서 제공&lt;/li&gt;
&lt;li&gt;흐름 단위로 분리하려면 Subcomponent와 커스텀 스코프를 사용&lt;/li&gt;
&lt;li&gt;테스트에서는 단위 테스트는 생성자로, 통합 테스트는 테스트 그래프로 교체하는 방식으로 접근&lt;/li&gt;
&lt;li&gt;모듈 include는 중복 선언이 생기지 않도록 구성&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Android</category>
      <author>어리둥절범고래</author>
      <guid isPermaLink="true">https://augustin26.tistory.com/140</guid>
      <comments>https://augustin26.tistory.com/entry/Android-%EC%95%B1%EC%97%90%EC%84%9C-Dagger-%EC%82%AC%EC%9A%A9#entry140comment</comments>
      <pubDate>Sun, 22 Feb 2026 14:13:21 +0900</pubDate>
    </item>
    <item>
      <title>Dagger 기본사항 정리</title>
      <link>https://augustin26.tistory.com/entry/Dagger-%EA%B8%B0%EB%B3%B8%EC%82%AC%ED%95%AD-%EC%A0%95%EB%A6%AC</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Dagger는 Java 및 Kotlin 애플리케이션에서 사용하는 정적 의존성 주입(Dependency Injection) 프레임워크입니다.&lt;br /&gt;Android 환경에서 객체 생성 및 종속성 관리를 자동화하여, 유지보수성과 테스트 용이성을 향상시킵니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;452&quot; data-start=&quot;275&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;314&quot; data-start=&quot;275&quot;&gt;&lt;b&gt;컴파일 타임 코드 생성&lt;/b&gt;으로 리플렉션 기반 DI보다 성능 우수&lt;/li&gt;
&lt;li data-end=&quot;339&quot; data-start=&quot;315&quot;&gt;&lt;b&gt;의존성 그래프 자동 생성 및 검증&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;373&quot; data-start=&quot;340&quot;&gt;&lt;b&gt;Factory 클래스 생성&lt;/b&gt;을 통한 객체 생성 관리&lt;/li&gt;
&lt;li data-end=&quot;405&quot; data-start=&quot;374&quot;&gt;&lt;b&gt;스코프 어노테이션&lt;/b&gt;을 활용한 객체 생명주기 관리&lt;/li&gt;
&lt;li data-end=&quot;452&quot; data-start=&quot;406&quot;&gt;&lt;b&gt;서브컴포넌트(Subcomponent)&lt;/b&gt; 지원으로 특정 흐름 단위 DI 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주요 구성 요소&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;452&quot; data-start=&quot;275&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;@Inject&lt;/b&gt;: 생성자&amp;nbsp;또는&amp;nbsp;필드에&amp;nbsp;붙여&amp;nbsp;의존성을&amp;nbsp;주입&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@Module&lt;/b&gt;: Dagger에&amp;nbsp;바인딩을&amp;nbsp;제공하는&amp;nbsp;클래스&amp;nbsp;정의&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@Provides&lt;/b&gt;: 생성자&amp;nbsp;주입이&amp;nbsp;불가능한&amp;nbsp;타입을&amp;nbsp;수동으로&amp;nbsp;제공&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@Component&lt;/b&gt;: 의존성&amp;nbsp;그래프를&amp;nbsp;생성하고&amp;nbsp;주입&amp;nbsp;지점을&amp;nbsp;연결&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@Singleton, @ActivityScope&lt;/b&gt;: 객체의&amp;nbsp;생명주기를&amp;nbsp;관리하는&amp;nbsp;스코프&amp;nbsp;어노테이션&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dagger를 사용하여 UserRepository 클래스의 간단한 팩토리를 만들어 보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1111&quot; data-origin-height=&quot;395&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ttJxK/dJMcajnyhNi/f8t3FdRJ4iwiGj86ziP2P1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ttJxK/dJMcajnyhNi/f8t3FdRJ4iwiGj86ziP2P1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ttJxK/dJMcajnyhNi/f8t3FdRJ4iwiGj86ziP2P1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FttJxK%2FdJMcajnyhNi%2Ff8t3FdRJ4iwiGj86ziP2P1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;750&quot; height=&quot;267&quot; data-origin-width=&quot;1111&quot; data-origin-height=&quot;395&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1770638883875&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class UserRepository(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dagger가&amp;nbsp;UserRepository를&amp;nbsp;생성하는&amp;nbsp;방법을&amp;nbsp;알&amp;nbsp;수&amp;nbsp;있도록&amp;nbsp;@Inject&amp;nbsp;주석을&amp;nbsp;UserRepository&amp;nbsp;생성자에&amp;nbsp;추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1770638983954&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Dagger는 @Inject가 붙은 생성자를 기준으로 해당 클래스의 인스턴스를 생성할 수 있습니다.
class UserRepository @Inject constructor(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UserRepository가&amp;nbsp;의존하는&amp;nbsp;클래스에도&amp;nbsp;@Inject&amp;nbsp;생성자가&amp;nbsp;필요합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1770639035901&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class UserLocalDataSource @Inject constructor() { ... }
class UserRemoteDataSource @Inject constructor() { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트는&amp;nbsp;Dagger의&amp;nbsp;의존성&amp;nbsp;그래프를&amp;nbsp;생성하는&amp;nbsp;진입점입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1770639051193&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
interface ApplicationGraph {
    fun repository(): UserRepository
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;applicationGraph.repository()&amp;nbsp;호출&amp;nbsp;시&amp;nbsp;새로운&amp;nbsp;인스턴스가&amp;nbsp;반환됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1770639083814&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create()
val userRepository: UserRepository = applicationGraph.repository()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dagger는&amp;nbsp;매번&amp;nbsp;새로운&amp;nbsp;인스턴스를&amp;nbsp;생성합니다.&lt;br /&gt;같은&amp;nbsp;인스턴스를&amp;nbsp;재사용하려면&amp;nbsp;스코프&amp;nbsp;어노테이션을&amp;nbsp;사용해야&amp;nbsp;합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1770639093172&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Singleton
@Component
interface ApplicationGraph {
    fun repository(): UserRepository
}

@Singleton
class UserRepository @Inject constructor(...) { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와&amp;nbsp;같이&amp;nbsp;구성하면&amp;nbsp;applicationGraph.repository()를&amp;nbsp;호출할&amp;nbsp;때마다&amp;nbsp;동일&amp;nbsp;인스턴스가&amp;nbsp;반환됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1770639170297&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create()

val userRepository: UserRepository = applicationGraph.repository()
val userRepository2: UserRepository = applicationGraph.repository()

assert(userRepository != userRepository2) // 새로운 인스턴스&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은&amp;nbsp;인스턴스를&amp;nbsp;재사용하려면&amp;nbsp;스코프&amp;nbsp;어노테이션을&amp;nbsp;사용해야&amp;nbsp;합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1770639210974&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Singleton
@Component
interface ApplicationGraph {
    fun repository(): UserRepository
}

@Singleton
class UserRepository @Inject constructor(...) { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요 시 사용자 정의 스코프도 생성 가능합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1770639233055&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Scope
@MustBeDocumented
@Retention(value = AnnotationRetention.RUNTIME)
annotation class MyCustomScope

@MyCustomScope
@Component
interface ApplicationGraph {
    fun repository(): UserRepository
}

@MyCustomScope
class UserRepository @Inject constructor(...) { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Component&amp;nbsp;인터페이스에&amp;nbsp;주석을&amp;nbsp;지정하는&amp;nbsp;데&amp;nbsp;사용된&amp;nbsp;것과&amp;nbsp;동일한&amp;nbsp;범위가&amp;nbsp;객체에&amp;nbsp;제공됩니다.&amp;nbsp;따라서&amp;nbsp;applicationGraph.repository()를&amp;nbsp;호출할&amp;nbsp;때마다&amp;nbsp;동일한&amp;nbsp;UserRepository&amp;nbsp;인스턴스를&amp;nbsp;얻습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1770639312385&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create()

val userRepository: UserRepository = applicationGraph.repository()
val userRepository2: UserRepository = applicationGraph.repository()

assert(userRepository == userRepository2) // 동일한 인스턴스&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Android</category>
      <author>어리둥절범고래</author>
      <guid isPermaLink="true">https://augustin26.tistory.com/139</guid>
      <comments>https://augustin26.tistory.com/entry/Dagger-%EA%B8%B0%EB%B3%B8%EC%82%AC%ED%95%AD-%EC%A0%95%EB%A6%AC#entry139comment</comments>
      <pubDate>Mon, 9 Feb 2026 21:16:13 +0900</pubDate>
    </item>
    <item>
      <title>Hilt 및 Dagger 주석 요약본</title>
      <link>https://augustin26.tistory.com/entry/Hilt-%EB%B0%8F-Dagger-%EC%A3%BC%EC%84%9D-%EC%9A%94%EC%95%BD%EB%B3%B8</link>
      <description>&lt;h3 data-end=&quot;109&quot; data-start=&quot;83&quot; data-ke-size=&quot;size23&quot;&gt;1. @HiltAndroidApp&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;158&quot; data-start=&quot;110&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;133&quot; data-start=&quot;110&quot;&gt;Application 클래스에 붙임&lt;/li&gt;
&lt;li data-end=&quot;158&quot; data-start=&quot;134&quot;&gt;Hilt의 시작점 (DI 그래프 생성됨)&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1770636610524&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@HiltAndroidApp class MyApp : Application()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-end=&quot;253&quot; data-start=&quot;224&quot; data-ke-size=&quot;size23&quot;&gt;2. @AndroidEntryPoint&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;320&quot; data-start=&quot;254&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;302&quot; data-start=&quot;254&quot;&gt;Activity, Fragment, Service 등 Android 컴포넌트에 붙임&lt;/li&gt;
&lt;li data-end=&quot;320&quot; data-start=&quot;303&quot;&gt;Hilt 주입 가능하게 만듦&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1770636618163&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@AndroidEntryPoint class MainActivity : AppCompatActivity()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-end=&quot;420&quot; data-start=&quot;402&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-end=&quot;420&quot; data-start=&quot;402&quot; data-ke-size=&quot;size23&quot;&gt;3. @Inject&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;448&quot; data-start=&quot;421&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;448&quot; data-start=&quot;421&quot;&gt;생성자나 필드에 붙여서 주입 받을 수 있게 함&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1770636642012&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class AnalyticsAdapter @Inject constructor(
 private val service: AnalyticsService
) { ... }

@AndroidEntryPoint
class MyActivity : AppCompatActivity() { 
 @Inject lateinit var adapter: AnalyticsAdapter
 ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;602&quot; data-start=&quot;577&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-end=&quot;602&quot; data-start=&quot;577&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;4. @HiltViewModel&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;632&quot; data-start=&quot;603&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;632&quot; data-start=&quot;603&quot;&gt;ViewModel에 붙여서 Hilt로 생성되게 함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1770636660495&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@HiltViewModel
class MyViewModel @Inject constructor( 
 private val adapter: AnalyticsAdapter,
 private val state: SavedStateHandle
): ViewModel() { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-end=&quot;764&quot; data-start=&quot;726&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-end=&quot;764&quot; data-start=&quot;726&quot; data-ke-size=&quot;size23&quot;&gt;5. @Module + @InstallIn(...)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;835&quot; data-start=&quot;765&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;785&quot; data-start=&quot;765&quot;&gt;직접 바인딩 로직 작성할 때 사용&lt;/li&gt;
&lt;li data-end=&quot;835&quot; data-start=&quot;786&quot;&gt;@InstallIn(SingletonComponent::class)처럼 범위 지정&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1770636673725&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@InstallIn(SingletonComponent::class)
@Module
class AnalyticsModule { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-end=&quot;944&quot; data-start=&quot;927&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-end=&quot;944&quot; data-start=&quot;927&quot; data-ke-size=&quot;size23&quot;&gt;6. @Binds&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;979&quot; data-start=&quot;945&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;979&quot; data-start=&quot;945&quot;&gt;인터페이스 &amp;harr; 구현체 바인딩 시 사용 (추상 메서드 필요)&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1770636686067&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@InstallIn(SingletonComponent::class)
@Module
abstract class AnalyticsModule {
 @Binds
 abstract fun bindsAnalyticsService(
   analyticsServiceImpl: AnalyticsServiceImpl
 ): AnalyticsService
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-end=&quot;1076&quot; data-start=&quot;1056&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-end=&quot;1076&quot; data-start=&quot;1056&quot; data-ke-size=&quot;size23&quot;&gt;7. @Provides&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1095&quot; data-start=&quot;1077&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1095&quot; data-start=&quot;1077&quot;&gt;수동으로 객체 생성할 때 사용(외부 라이브러리)&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1770636693869&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@InstallIn(SingletonComponent::class)
@Module
class AnalyticsModule {
 @Provides
 fun providesAnalyticsService(
   converterFactory: GsonConverterFactory
 ): AnalyticsService {
   return Retrofit.Builder()
     .baseUrl(&quot;https://example.com&quot;)
     .addConverterFactory(converterFactory)
     .build()
     .create(AnalyticsService::class.java)
 }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-end=&quot;1222&quot; data-start=&quot;1167&quot; data-ke-size=&quot;size23&quot;&gt;8. 스코프 어노테이션 (@Singleton, @ActivityScoped, 등)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1240&quot; data-start=&quot;1223&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1240&quot; data-start=&quot;1223&quot;&gt;주입된 객체의 생명주기 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1770636736904&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Singleton
class AnalyticsAdapter @Inject constructor(
 private val service: AnalyticsService
) { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 data-end=&quot;1332&quot; data-start=&quot;1313&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-end=&quot;1332&quot; data-start=&quot;1313&quot; data-ke-size=&quot;size23&quot;&gt;9. 컨텍스트 어노테이션&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1360&quot; data-start=&quot;1333&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1360&quot; data-start=&quot;1333&quot;&gt;어떤 Context를 주입할지 명확히 구분&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1770636749358&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Singleton
class AnalyticsAdapter @Inject constructor(
 @ApplicationContext val context: Context,
 private val service: AnalyticsService
) { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-end=&quot;1446&quot; data-start=&quot;1423&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-end=&quot;1446&quot; data-start=&quot;1423&quot; data-ke-size=&quot;size23&quot;&gt;10. @EntryPoint&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1498&quot; data-start=&quot;1447&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1498&quot; data-start=&quot;1447&quot;&gt;ContentProvider 등 &lt;b&gt;Hilt 미지원 클래스&lt;/b&gt;에서 의존성 접근할 때 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1770636759273&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class MyContentProvider(): ContentProvider {
 @InstallIn(SingletonComponent::class)
 @EntryPoint
 interface MyContentProviderEntryPoint {
   fun analyticsService(): AnalyticsService
 }

 override fun query(...): Cursor {
   val appContext = 
     context?.applicationContext 
     ?: throw IllegalStateException()

   val entryPoint = 
     EntryPointAccessors.fromApplication(
       appContext, 
       MyContentProviderEntryPoint::class.java)

   val analyticsService = 
     entryPoint.analyticsService()

   ...
 }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100.581%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.846%;&quot;&gt;&lt;b&gt; 어노테이션 &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 15.8253%;&quot;&gt;&lt;b&gt; 용도 &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 26.3093%;&quot;&gt;&lt;b&gt; 설명 &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 39.717%;&quot;&gt;&lt;b&gt; 코드 예시 &lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.846%;&quot;&gt;&lt;b&gt;@HiltAndroidApp&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 15.8253%;&quot;&gt;애플리케이션 설정&lt;/td&gt;
&lt;td style=&quot;width: 26.3093%;&quot;&gt;Hilt 코드 생성을 시작함. 반드시 Application 클래스에 선언&lt;/td&gt;
&lt;td style=&quot;width: 39.717%;&quot;&gt;@HiltAndroidApp class MyApplication : Application()&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.846%;&quot;&gt;&lt;b&gt;@AndroidEntryPoint&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 15.8253%;&quot;&gt;Android 클래스에 DI 컨테이너 추가&lt;/td&gt;
&lt;td style=&quot;width: 26.3093%;&quot;&gt;Activity, Fragment, Service 등에 Hilt 주입 허용&lt;/td&gt;
&lt;td style=&quot;width: 39.717%;&quot;&gt;@AndroidEntryPoint class MyActivity : AppCompatActivity()&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.846%;&quot;&gt;&lt;b&gt;@HiltViewModel&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 15.8253%;&quot;&gt;ViewModel 주입&lt;/td&gt;
&lt;td style=&quot;width: 26.3093%;&quot;&gt;ViewModel에 Hilt로 의존성 주입 설정&lt;/td&gt;
&lt;td style=&quot;width: 39.717%;&quot;&gt;@HiltViewModel class MyViewModel @Inject constructor(...)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.846%;&quot;&gt;&lt;b&gt;@Inject&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 15.8253%;&quot;&gt;생성자 / 필드 주입&lt;/td&gt;
&lt;td style=&quot;width: 26.3093%;&quot;&gt;어떤 생성자를 사용하고 어떤 의존성이 필요한지 명시&lt;/td&gt;
&lt;td style=&quot;width: 39.717%;&quot;&gt;class Adapter @Inject constructor(service: Service)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.846%;&quot;&gt;&lt;b&gt;필드 @Inject&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 15.8253%;&quot;&gt;필드에 주입&lt;/td&gt;
&lt;td style=&quot;width: 26.3093%;&quot;&gt;@AndroidEntryPoint 클래스 내의 lateinit var에 사용 (private 불가)&lt;/td&gt;
&lt;td style=&quot;width: 39.717%;&quot;&gt;@Inject lateinit var adapter: Adapter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.846%;&quot;&gt;&lt;b&gt;@Module&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 15.8253%;&quot;&gt;수동 바인딩 제공&lt;/td&gt;
&lt;td style=&quot;width: 26.3093%;&quot;&gt;생성자 주입이 불가능한 타입을 수동 바인딩하는 클래스&lt;/td&gt;
&lt;td style=&quot;width: 39.717%;&quot;&gt;@Module class AnalyticsModule { ... }&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.846%;&quot;&gt;&lt;b&gt;@InstallIn(...)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 15.8253%;&quot;&gt;바인딩 범위 지정&lt;/td&gt;
&lt;td style=&quot;width: 26.3093%;&quot;&gt;어떤 Hilt 컴포넌트(Singleton 등)에 바인딩할지 설정&lt;/td&gt;
&lt;td style=&quot;width: 39.717%;&quot;&gt;@InstallIn(SingletonComponent::class)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.846%;&quot;&gt;&lt;b&gt;@Binds&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 15.8253%;&quot;&gt;인터페이스 &amp;harr; 구현체 바인딩&lt;/td&gt;
&lt;td style=&quot;width: 26.3093%;&quot;&gt;추상 메서드를 사용해 인터페이스 타입에 구현체 바인딩&lt;/td&gt;
&lt;td style=&quot;width: 39.717%;&quot;&gt;@Binds abstract fun bindService(impl: ServiceImpl): Service&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.846%;&quot;&gt;&lt;b&gt;@Provides&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 15.8253%;&quot;&gt;수동 생성 바인딩&lt;/td&gt;
&lt;td style=&quot;width: 26.3093%;&quot;&gt;직접 객체 생성 로직이 필요한 경우 사용&lt;/td&gt;
&lt;td style=&quot;width: 39.717%;&quot;&gt;@Provides fun provideService(): Service&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.846%;&quot;&gt;&lt;b&gt;스코프 어노테이션&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 15.8253%;&quot;&gt;생명주기 바인딩&lt;/td&gt;
&lt;td style=&quot;width: 26.3093%;&quot;&gt;@Singleton, @ActivityScoped 등으로 객체 수명 관리&lt;/td&gt;
&lt;td style=&quot;width: 39.717%;&quot;&gt;@Singleton class Repo @Inject constructor(...)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.846%;&quot;&gt;&lt;b&gt;@ApplicationContext@ActivityContext&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 15.8253%;&quot;&gt;컨텍스트 구분 바인딩&lt;/td&gt;
&lt;td style=&quot;width: 26.3093%;&quot;&gt;Context가 애플리케이션용인지, 액티비티용인지 구분&lt;/td&gt;
&lt;td style=&quot;width: 39.717%;&quot;&gt;@ApplicationContext val context: Context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.846%;&quot;&gt;&lt;b&gt;@EntryPoint&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 15.8253%;&quot;&gt;Hilt 미지원 클래스에 주입&lt;/td&gt;
&lt;td style=&quot;width: 26.3093%;&quot;&gt;ContentProvider 등 Hilt 미지원 클래스에서 주입할 때 사용&lt;/td&gt;
&lt;td style=&quot;width: 39.717%;&quot;&gt;@EntryPoint interface MyEntryPoint { fun service(): Service }&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Android</category>
      <author>어리둥절범고래</author>
      <guid isPermaLink="true">https://augustin26.tistory.com/138</guid>
      <comments>https://augustin26.tistory.com/entry/Hilt-%EB%B0%8F-Dagger-%EC%A3%BC%EC%84%9D-%EC%9A%94%EC%95%BD%EB%B3%B8#entry138comment</comments>
      <pubDate>Mon, 9 Feb 2026 20:34:43 +0900</pubDate>
    </item>
    <item>
      <title>Hilt 테스트 가이드</title>
      <link>https://augustin26.tistory.com/entry/Hilt-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B0%80%EC%9D%B4%EB%93%9C</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;Hilt와&amp;nbsp;같은&amp;nbsp;종속&amp;nbsp;항목&amp;nbsp;삽입&amp;nbsp;프레임워크를&amp;nbsp;사용하여&amp;nbsp;얻을&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;이점&amp;nbsp;중&amp;nbsp;하나는&amp;nbsp;코드를&amp;nbsp;더&amp;nbsp;쉽게&amp;nbsp;테스트할&amp;nbsp;수&amp;nbsp;있다는&amp;nbsp;점입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;테스트&amp;nbsp;Application&amp;nbsp;클래스&amp;nbsp;구성&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;@HiltAndroidApp이&amp;nbsp;선언된&amp;nbsp;실제&amp;nbsp;앱의&amp;nbsp;Application&amp;nbsp;클래스는&amp;nbsp;테스트에서&amp;nbsp;무시되고,&amp;nbsp;HiltTestApplication이&amp;nbsp;대신&amp;nbsp;사용됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;보통 Hilt 테스트용 Application 클래스는 Hilt가 기본으로 제공하는 HiltTestApplication을 Application 클래스로 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1770636168495&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;application
    android:name=&quot;dagger.hilt.android.testing.HiltTestApplication&quot;
    ... /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;혹은 AndroidJUnitRunner를 확장하는 맞춤 클래스를 생성합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1770636174294&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 테스트 전용 manifest를 따로 둘 경우 아래처럼 설정 가능:
android {
    defaultConfig {
        testInstrumentationRunner &quot;com.example.android.dagger.CustomTestRunner&quot;
    }
}


package com.example.android.dagger.CustomTestRunner

class CustomTestRunner : AndroidJUnitRunner() {
    override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application {
        // 플래이버나 빌드타입에 따라 다른 Application 사용 가능
        val testAppClassName = when (BuildConfig.FLAVOR) {
            &quot;demo&quot; -&amp;gt; &quot;com.example.DemoHiltTestApplication_Application&quot;
            else -&amp;gt; &quot;com.example.DefaultHiltTestApplication_Application&quot;
        }

        return super.newApplication(cl, testAppClassName, context)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;그런데 만약, 아래와 같은&amp;nbsp;커스텀 Application을 사용하고 있는 경우 HiltTestApplication을 그냥 쓰면 앱 설정이 깨지게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1770636179308&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class BaseApplication : Application()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;이런 경우, @CustomTestApplication을 사용하면 매개변수로 전달한 애플리케이션을 확장하는 Application 클래스를 생성합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1770636183828&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@CustomTestApplication(BaseApplication::class)
interface HiltTestApplication

// 힐트 내부 코드
class HiltTestApplication_Application : BaseApplication()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;테스트용&amp;nbsp;HiltRule&amp;nbsp;사용&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;Hilt는 테스트 시 의존성 주입을 트리거하고 Hilt 컴포넌트를 초기화하기 위한 전용 Rule을 제공합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769923498376&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@HiltAndroidTest
class MyTest {

    @get:Rule
    val hiltRule = HiltAndroidRule(this)

    @Before
    fun init() {
        hiltRule.inject() // 주입 실행
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;@HiltAndroidTest&lt;/b&gt;:&amp;nbsp;테스트&amp;nbsp;클래스임을&amp;nbsp;나타내는&amp;nbsp;어노테이션&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;HiltAndroidRule&lt;/b&gt;: 테스트 실행 전에 DI 그래프를 초기화&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;inject()&lt;/b&gt;: 주입 수행 (테스트용 필드에 @Inject 가능)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #ffffff; text-align: start; --darkreader-inline-color: var(--darkreader-text-ffffff, #ffffff);&quot; data-ke-size=&quot;size16&quot; data-darkreader-inline-color=&quot;&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;&lt;br /&gt;계측 테스트(Instrumented Test)에서 Hilt 사용&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1770636032591&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@HiltAndroidTest
@RunWith(AndroidJUnit4::class)
class LoginScreenTest {

    @get:Rule
    val hiltRule = HiltAndroidRule(this)

    @Before
    fun setUp() {
        hiltRule.inject()
    }

    @Inject lateinit var userRepository: UserRepository

    @Test
    fun testLoginSuccess() {
        // 주입된 FakeUserRepository 사용 가능
        // 테스트 UI 검증 코드 작성
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #ffffff; text-align: start; --darkreader-inline-color: var(--darkreader-text-ffffff, #ffffff);&quot; data-ke-size=&quot;size16&quot; data-darkreader-inline-color=&quot;&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;JUnit에서 @Rule은 테스트 실행 전에 필수 작업을 자동으로 해주며,&amp;nbsp;@Inject로 실제 앱과 동일하게 테스트 대상에 의존성을 주입해 줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #ffffff; text-align: start; --darkreader-inline-color: var(--darkreader-text-ffffff, #ffffff);&quot; data-ke-size=&quot;size16&quot; data-darkreader-inline-color=&quot;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #ffffff; text-align: start; --darkreader-inline-color: var(--darkreader-text-ffffff, #ffffff);&quot; data-ke-size=&quot;size16&quot; data-darkreader-inline-color=&quot;&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;이로써 모듈만 테스트 전용으로 교체하여 유연한 테스트 구성이 가능합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #ffffff; text-align: start; --darkreader-inline-color: var(--darkreader-text-ffffff, #ffffff);&quot; data-ke-size=&quot;size16&quot; data-darkreader-inline-color=&quot;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #ffffff; text-align: start; --darkreader-inline-color: var(--darkreader-text-ffffff, #ffffff);&quot; data-ke-size=&quot;size16&quot; data-darkreader-inline-color=&quot;&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;만약 테스트 클래스에 2개 이상의 Rule을 쓸 때 실행 순서가 꼬이면 테스트가 실패하거나 Hilt가 동작 안 할 수 있기 때문에, Hilt는 여러 기능을 제공합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1770636318230&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// RuleChain 사용
@get:Rule
var rule = RuleChain
    .outerRule(HiltAndroidRule(this)) // 먼저 실행됨
    .around(SettingsActivityTestRule(...)) // 그다음 실행

// order 속성 사용
@get:Rule(order = 0) // 먼저 실행됨
var hiltRule = HiltAndroidRule(this)

@get:Rule(order = 1) // 나중 실행됨
var activityRule = SettingsActivityTestRule(...)&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #ffffff; text-align: start; --darkreader-inline-color: var(--darkreader-text-ffffff, #ffffff);&quot; data-ke-size=&quot;size16&quot; data-darkreader-inline-color=&quot;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #ffffff; text-align: start; --darkreader-inline-color: var(--darkreader-text-ffffff, #ffffff);&quot; data-ke-size=&quot;size16&quot; data-darkreader-inline-color=&quot;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;테스트용&amp;nbsp;의존성&amp;nbsp;재정의&amp;nbsp;(Test&amp;nbsp;Binding)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;실제 앱에서는 @Module을 통해 의존성을 제공하지만, 테스트에서는 이를 재정의(override) 하여 Fake / Stub / Mock 구현을 주입해야 할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1770633960673&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Module
@InstallIn(SingletonComponent::class)
abstract class AnalyticsModule {

  @Singleton
  @Binds
  abstract fun bindAnalyticsService(
    analyticsServiceImpl: AnalyticsServiceImpl
  ): AnalyticsService
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;이때 Hilt는 @TestInstallIn 어노테이션을 통해, 기존 의존성을 테스트 전용 모듈로 교체할 수 있도록 지원합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769924151291&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Module
@TestInstallIn(
    components = [SingletonComponent::class],
    replaces = [AnalyticsModule::class] // 기존 모듈을 대체
)
abstract class FakeAnalyticsModule {

  @Singleton
  @Binds
  abstract fun bindAnalyticsService(
    fakeAnalyticsService: FakeAnalyticsService
  ): AnalyticsService
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;이 방법을 공통된 Fake 의존성이 필요한 경우에 적합하며, &lt;span style=&quot;text-align: start; --darkreader-inline-color: var(--darkreader-text-ffffff, #ffffff);&quot; data-darkreader-inline-color=&quot;&quot;&gt;test/나 androidTest/ 폴더에 있는 모든 테스트에서 동일한 Fake 구현이 주입됩니다.&lt;/span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start; --darkreader-inline-color: var(--darkreader-text-ffffff, #ffffff); background-color: #ffffff;&quot; data-darkreader-inline-color=&quot;&quot;&gt;모든 테스트가 아닌, 특정 테스트 클래스에서만 다른 결합(Fake 등)을 사용하고 싶은 경우에는 @TestInstallIn&amp;nbsp;대신&amp;nbsp;@UninstallModules&amp;nbsp;어노테이션을&amp;nbsp;사용하는&amp;nbsp;것이&amp;nbsp;더&amp;nbsp;적합합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1770634464574&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@UninstallModules(AnalyticsModule::class) // 기존 모듈 제거
@HiltAndroidTest
class SettingsActivityTest {

  @Module
  @InstallIn(SingletonComponent::class)
  abstract class TestModule {

    @Singleton
    @Binds
    abstract fun bindAnalyticsService(
      fakeAnalyticsService: FakeAnalyticsService
    ): AnalyticsService
  }

  ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;또는, @BindValue를 통해 테스트 클래스 내에서 바로 의존성을 바인딩할 수도 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1770634801640&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@UninstallModules(AnalyticsModule::class)
@HiltAndroidTest
class SettingsActivityTest {

  @BindValue @JvmField
  val analyticsService: AnalyticsService = FakeAnalyticsService()

  ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;단위&amp;nbsp;테스트&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;단위 테스트에서는 Android Framework 클래스 접근이 불가능하기 때문에, 기본적으로 단위 테스트에서는 Hilt를 사용하는 것이 제한적입니다. &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;HiltAndroidTest, HiltAndroidRule, HiltTestApplication 등의 기능은 계측 테스트 환경(Instrumented Test)에서만 정상적으로 작동합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;단위 테스트에서는 아래와 같이 처리합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;Hilt를 사용하지 않고 Dagger 컴포넌트를 직접 생성&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;ViewModel, Repository 등은 Hilt 없이 직접 생성하여 테스트&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;또는, @InstallIn 대신 수동 주입으로 단위 테스트 범위 내에서 관리&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;Hilt 테스트 요약&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 126px;&quot; border=&quot;1&quot; data-end=&quot;3586&quot; data-start=&quot;3263&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt; 항목 &lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt; 사용법 &lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt; 목적 &lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot; data-end=&quot;3361&quot; data-start=&quot;3306&quot;&gt;
&lt;td style=&quot;height: 21px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;3327&quot; data-start=&quot;3306&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;@HiltAndroidTest&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;3341&quot; data-start=&quot;3327&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;테스트 클래스에 선언&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;3361&quot; data-start=&quot;3341&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;테스트 전용 DI 그래프 생성&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot; data-end=&quot;3419&quot; data-start=&quot;3362&quot;&gt;
&lt;td style=&quot;height: 21px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;3382&quot; data-start=&quot;3362&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;HiltAndroidRule&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;3399&quot; data-start=&quot;3382&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;테스트 실행 전 주입 처리&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;3419&quot; data-start=&quot;3399&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;inject() 호출 필요&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot; data-end=&quot;3470&quot; data-start=&quot;3420&quot;&gt;
&lt;td style=&quot;height: 21px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;3439&quot; data-start=&quot;3420&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;@TestInstallIn&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot; data-end=&quot;3448&quot; data-start=&quot;3439&quot; data-col-size=&quot;sm&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;모듈 재정의&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot; data-end=&quot;3470&quot; data-start=&quot;3448&quot; data-col-size=&quot;sm&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;테스트용 Fake/Mock 바인딩&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot; data-end=&quot;3529&quot; data-start=&quot;3471&quot;&gt;
&lt;td style=&quot;height: 21px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;3495&quot; data-start=&quot;3471&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;HiltTestApplication&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot; data-end=&quot;3508&quot; data-start=&quot;3495&quot; data-col-size=&quot;sm&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;테스트 앱으로 설정&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot; data-end=&quot;3529&quot; data-start=&quot;3508&quot; data-col-size=&quot;sm&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;실제 Application 대체&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot; data-end=&quot;3586&quot; data-start=&quot;3530&quot;&gt;
&lt;td style=&quot;height: 21px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;3539&quot; data-start=&quot;3530&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;단위 테스트&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;3558&quot; data-start=&quot;3539&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;기본적으로 Hilt 미지원&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot; data-end=&quot;3586&quot; data-start=&quot;3558&quot; data-col-size=&quot;sm&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;수동 주입 또는 Dagger 직접 사용 필요&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Android</category>
      <author>어리둥절범고래</author>
      <guid isPermaLink="true">https://augustin26.tistory.com/137</guid>
      <comments>https://augustin26.tistory.com/entry/Hilt-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B0%80%EC%9D%B4%EB%93%9C#entry137comment</comments>
      <pubDate>Tue, 3 Feb 2026 08:56:03 +0900</pubDate>
    </item>
    <item>
      <title>Jetpack 라이브러리 Hilt</title>
      <link>https://augustin26.tistory.com/entry/Jetpack-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-Hilt</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드 앱 개발에서 &lt;b&gt;ViewModel, Navigation, Compose, WorkManager&lt;/b&gt;와 같은 Jetpack 컴포넌트는 거의 필수적으로 사용되는 라이브러리입니다. Hilt는 이러한 Jetpack 컴포넌트들과 자연스럽게 통합되도록 설계되어 있으며, 의존성 주입을 보다 일관적이고 효율적으로 구성할 수 있도록 지원합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ViewModel에 의존성 주입하기&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Hilt는 ViewModel에 의존성을 주입하기 위한 전용 어노테이션인 @HiltViewModel을 제공합니다. 이를 통해 ViewModel도 다른 클래스들과 마찬가지로 Hilt의 DI 그래프에 포함시킬 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1769922742865&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@HiltViewModel
class ExampleViewModel @Inject constructor(
    private val repository: ExampleRepository
) : ViewModel() {
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 ViewModel을 사용하는 Activity나 Fragment는 @AndroidEntryPoint 어노테이션을 선언하고, by viewModels() 또는 by hiltNavGraphViewModels() 같은 Hilt 지원 팩토리를 통해 ViewModel을 쉽게 가져올 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1769922527768&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@AndroidEntryPoint
class ExampleFragment : Fragment() {
    private val viewModel: ExampleViewModel by viewModels() // or hiltNavGraphViewModels()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hilt는 ViewModel을 ViewModelComponent 라는 독립된 컴포넌트에서 관리합니다.&lt;br /&gt;이&amp;nbsp;컴포넌트는&amp;nbsp;@InstallIn(ViewModelComponent::class)를&amp;nbsp;통해&amp;nbsp;특정&amp;nbsp;의존성을&amp;nbsp;ViewModel&amp;nbsp;범위로&amp;nbsp;설정할&amp;nbsp;수&amp;nbsp;있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Navigation과&amp;nbsp;함께&amp;nbsp;사용하기&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Navigation&amp;nbsp;구성에서&amp;nbsp;화면&amp;nbsp;전환&amp;nbsp;간&amp;nbsp;ViewModel을&amp;nbsp;공유하려면&amp;nbsp;navigation-graph-scope를&amp;nbsp;기반으로&amp;nbsp;하는&amp;nbsp;ViewModel&amp;nbsp;접근이&amp;nbsp;필요합니다.&amp;nbsp;이를&amp;nbsp;위해&amp;nbsp;Hilt는&amp;nbsp;hiltNavigationGraphViewModels()&amp;nbsp;라는&amp;nbsp;확장&amp;nbsp;함수를&amp;nbsp;제공합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1769921827489&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private val viewModel: ExampleViewModel by hiltNavGraphViewModels(R.id.nav_graph)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식을 통해 특정 네비게이션 그래프 범위 내에서 ViewModel 인스턴스를 공유할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;WorkManager와&amp;nbsp;함께&amp;nbsp;사용하기&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;WorkManager는&amp;nbsp;백그라운드&amp;nbsp;작업을&amp;nbsp;수행할&amp;nbsp;때&amp;nbsp;사용하는&amp;nbsp;Jetpack&amp;nbsp;구성요소입니다.&lt;br /&gt;Hilt는&amp;nbsp;@HiltWorker와&amp;nbsp;@AssistedInject를&amp;nbsp;활용하여&amp;nbsp;Worker&amp;nbsp;클래스에도&amp;nbsp;의존성을&amp;nbsp;주입할&amp;nbsp;수&amp;nbsp;있도록&amp;nbsp;지원합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1769922251675&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Worker 정의
@HiltWorker
class ExampleWorker @AssistedInject constructor(
    @Assisted appContext: Context,
    @Assisted workerParams: WorkerParameters,
    private val repository: ExampleRepository
) : Worker(appContext, workerParams) {
    
    override fun doWork(): Result {
        repository.doBackgroundTask()
        return Result.success()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Application&amp;nbsp;클래스에서는&amp;nbsp;HiltWorkerFactory를&amp;nbsp;설정하여&amp;nbsp;WorkManager가&amp;nbsp;주입&amp;nbsp;가능한&amp;nbsp;Worker&amp;nbsp;인스턴스를&amp;nbsp;생성할&amp;nbsp;수&amp;nbsp;있도록&amp;nbsp;구성해야&amp;nbsp;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1769922282064&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// WorkerFactory 설정
@HiltAndroidApp
class MyApplication : Application(), Configuration.Provider {

    @Inject lateinit var workerFactory: HiltWorkerFactory

    override fun getWorkManagerConfiguration() =
        Configuration.Builder()
            .setWorkerFactory(workerFactory)
            .build()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SavedStateHandle 사용하기&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Hilt는&amp;nbsp;SavedStateHandle을&amp;nbsp;ViewModel&amp;nbsp;생성자에&amp;nbsp;주입하는&amp;nbsp;것도&amp;nbsp;지원합니다.&lt;br /&gt;Navigation-Compose와&amp;nbsp;함께&amp;nbsp;사용할&amp;nbsp;때&amp;nbsp;route&amp;nbsp;argument를&amp;nbsp;처리하는&amp;nbsp;데&amp;nbsp;특히&amp;nbsp;유용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1769922402752&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@HiltViewModel
class DetailViewModel @Inject constructor(
    savedStateHandle: SavedStateHandle
) : ViewModel() {
    val itemId: String = savedStateHandle[&quot;itemId&quot;] ?: &quot;&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기능을 통해 프로세스 종료 후 복원 상황에서도 필요한 데이터를 안정적으로 관리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;Jetpack Compose에서 Hilt 사용하기&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Compose에서도 ViewModel에 Hilt를 통한 의존성 주입이 가능하며, hiltViewModel()을 통해 @HiltViewModel이 붙은 ViewModel을 직접 사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1769922416842&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun HomeScreen() {
    val viewModel: HomeViewModel = hiltViewModel()
    val state by viewModel.state.collectAsState()

    Text(text = state.message)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@AndroidEntryPoint가 선언된 Activity나 NavHost 내에서 이와 같은 방식으로 ViewModel을 직접 주입할 수 있으며,&lt;br /&gt;내비게이션&amp;nbsp;목적지&amp;nbsp;내에서도&amp;nbsp;동일하게&amp;nbsp;사용할&amp;nbsp;수&amp;nbsp;있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1769922427916&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;composable(&quot;login&quot;) {
    val viewModel: LoginViewModel = hiltViewModel()
    LoginScreen(viewModel)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Android</category>
      <author>어리둥절범고래</author>
      <guid isPermaLink="true">https://augustin26.tistory.com/136</guid>
      <comments>https://augustin26.tistory.com/entry/Jetpack-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-Hilt#entry136comment</comments>
      <pubDate>Sun, 1 Feb 2026 14:13:26 +0900</pubDate>
    </item>
    <item>
      <title>Hilt 멀티 모듈</title>
      <link>https://augustin26.tistory.com/entry/Hilt-%EB%A9%80%ED%8B%B0-%EB%AA%A8%EB%93%88</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;모듈화를 통해 기능별로 코드를 분리하면 코드의 응집도와 재사용성이 높아지고, 테스트 및 배포가 용이해지는 장점이 있습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 Hilt가 코드를 생성하려면, Hilt를 사용하는 모든 Gradle 모듈에 접근할 수 있어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱 모듈은 다른 모듈에서 정의된 모든 Hilt 모듈(@Module) 및 생성자 주입 클래스(@Inject) 들이 전이 종속성(Transitive Dependency)으로 포함되어 있어야 합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;전이 종속성(Transitive Dependency)이란, 어떤 모듈이 직접 의존하는 다른 모듈이 다시 또 다른 모듈을 의존하고 있을 때, 최초의 모듈이 해당 모듈을 직접 선언하지 않았음에도 불구하고 간접적으로 의존하게 되는 관계를 의미합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-start-index=&quot;225&quot;&gt;&lt;span data-start-index=&quot;259&quot;&gt;Feature 모듈 구조에서는 의존성 방향이 일반적인 방식과 정반대로 흐릅니다.&lt;/span&gt;&lt;/div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 84px;&quot; border=&quot;1&quot; data-start-index=&quot;396&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt; 구분 &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt; 일반 Gradle 모듈 &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;Feature&amp;nbsp;모듈&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;432&quot;&gt;&lt;b data-start-index=&quot;432&quot;&gt;의존성 방향&lt;/b&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;438&quot;&gt;&lt;span data-start-index=&quot;438&quot;&gt;상위(App) 모듈이 하위 모듈을 참조함&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;460&quot;&gt;&lt;span data-start-index=&quot;460&quot;&gt;상위(App) 모듈을 참조하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b data-start-index=&quot;477&quot;&gt;의존성 역전&lt;/b&gt;&lt;span data-start-index=&quot;483&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;구조&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;486&quot;&gt;&lt;b data-start-index=&quot;486&quot;&gt;컴파일 타임 가시성&lt;/b&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;496&quot;&gt;&lt;span data-start-index=&quot;496&quot;&gt;App 모듈이 모든 하위 코드를 볼 수 있음&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;520&quot;&gt;&lt;span data-start-index=&quot;520&quot;&gt;App 모듈이 Feature&amp;nbsp;모듈의 코드를 볼 수 없음&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;562&quot;&gt;&lt;b data-start-index=&quot;562&quot;&gt;Hilt 주석 처리&lt;/b&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;572&quot;&gt;@AndroidEntryPoint&lt;span data-start-index=&quot;590&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;등 표준 주석 사용 가능&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;604&quot;&gt;&lt;span data-start-index=&quot;604&quot;&gt;Hilt 자동 주입을 사용할 수 없으므로, &lt;br /&gt;수동으로&amp;nbsp;구성하는&amp;nbsp;Dagger&amp;nbsp;방식을&amp;nbsp;사용&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@EntryPoint는 Hilt가 직접 관리할 수 없는 영역(Feature 모듈 등)에서 Hilt의 의존성 그래프에 접근할 수 있도록 열어둔 특별한 게이트웨이입니다. Feature 모듈은 App 모듈을 의존하고 있으므로, App&amp;nbsp;모듈&amp;nbsp;내에&amp;nbsp;구축된&amp;nbsp;Hilt의&amp;nbsp;SingletonComponent에서&amp;nbsp;필요한&amp;nbsp;객체를&amp;nbsp;참조하겠다고&amp;nbsp;명시하는&amp;nbsp;것과&amp;nbsp;같습니다. 이는 Hilt의 자동 주입 혜택을 받지 못하는 환경에서도 의존성 그래프의 연속성을 유지해 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Feature&amp;nbsp;모듈 내 LoginActivity에서 Hilt의 의존성을 주입받는 예시입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1769920117621&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;:app
├── di/
│   └── AppModule.kt                  // UserRepository 제공
├── entrypoint/
│   └── LoginModuleDependencies.kt   // EntryPoint 인터페이스
└── App.kt                            // @HiltAndroidApp

:feature_login
├── di/
│   └── LoginComponent.kt            // Dagger 컴포넌트 정의
├── data/
│   └── LoginAnalyticsAdapter.kt     // @Inject 사용 클래스
└── ui/
    └── LoginActivity.kt             // 의존성 주입 수행&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. App 모듈에 @EntryPoint 인터페이스 선언&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Feature&amp;nbsp;모듈이 App 모듈의 그래프에서 무엇을 꺼내올지 정의하는 계약(Contract)을 만듭니다. 이 인터페이스는 Hilt가 접근 가능한 app 모듈 내에 위치해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1769918487303&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// app 모듈 내 LoginModuleDependencies.kt
@EntryPoint
@InstallIn(SingletonComponent::class) // Hilt의 Singleton 범위를 사용하도록 지정
interface LoginModuleDependencies {
    // Feature 모듈에서 가져다 쓸 의존성을 메서드 형태로 정의
    @AuthInterceptorOkHttpClient
    fun okHttpClient(): OkHttpClient 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;2. Feature&amp;nbsp;모듈 내에서 Dagger 구성요소 생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Feature&amp;nbsp;모듈 내부에서는 Hilt의 @AndroidEntryPoint를 사용할 수 없으므로, 표준 Dagger(@Component)를 사용하여 위에서 정의한 엔트리 포인트와 협업하는 브릿지를 만듭니다.&lt;/p&gt;
&lt;pre id=&quot;code_1769918492975&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// login Feature 모듈 내 LoginComponent.kt
@Component(dependencies = [LoginModuleDependencies::class]) // 엔트리 포인트를 의존성으로 지정
interface LoginComponent {
    fun inject(activity: LoginActivity)

    @Component.Builder
    interface Builder {
        fun context(@BindsInstance context: Context): Builder
        // Hilt 그래프에서 추출한 엔트리 포인트를 전달받는 통로
        fun appDependencies(loginModuleDependencies: LoginModuleDependencies): Builder
        fun build(): LoginComponent
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;3. EntryPointAccessors를 사용하여 실제 객체 인스턴스 획득&lt;/b&gt; &lt;br /&gt;이제 LoginActivity에서 Hilt 그래프를 직접 열고 객체를 추출합니다. 여기서 EntryPointAccessors.fromApplication은 Hilt가 관리하는 Application 컨텍스트에서 우리가 정의한 엔트리 포인트를 찾아오는 역할을 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1769918499950&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// login Feature 모듈 내 LoginActivity.kt
class LoginActivity : AppCompatActivity() {
    @Inject lateinit var loginAnalyticsAdapter: LoginAnalyticsAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        // 1. EntryPointAccessors를 통해 Application 범위의 Hilt 그래프에서 엔트리 포인트 계약(Contract)을 추출
        val dependencies = EntryPointAccessors.fromApplication(
            applicationContext,
            LoginModuleDependencies::class.java
        )

        // 2. 추출한 엔트리 포인트를 수동으로 구성한 Dagger Component에 전달하여 주입 수행
        DaggerLoginComponent.builder()
            .context(this)
            .appDependencies(dependencies) // Hilt에서 꺼내온 의존성 주입
            .build()
            .inject(this)

        super.onCreate(savedInstanceState)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Android</category>
      <author>어리둥절범고래</author>
      <guid isPermaLink="true">https://augustin26.tistory.com/135</guid>
      <comments>https://augustin26.tistory.com/entry/Hilt-%EB%A9%80%ED%8B%B0-%EB%AA%A8%EB%93%88#entry135comment</comments>
      <pubDate>Sun, 1 Feb 2026 13:43:32 +0900</pubDate>
    </item>
    <item>
      <title>Hilt를 사용한 의존성 주입</title>
      <link>https://augustin26.tistory.com/entry/Hilt%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%9C-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt; &lt;span style=&quot;text-align: start; --darkreader-inline-bgcolor: var(--darkreader-background-262828, #2d3131); --darkreader-inline-color: var(--darkreader-text-ffffff, #ffffff);&quot; data-darkreader-inline-bgcolor=&quot;&quot; data-darkreader-inline-color=&quot;&quot;&gt;구성요소에 모듈을 설치하면 이 구성요소의 다른 바인딩 또는 구성요소 계층 구조에서 그 아래에 있는 하위 구성요소의 다른 바인딩의 종속 항목으로 설치된 모듈의 바인딩에 액세스할 수 있습니다.&lt;/span&gt; Hilt는&amp;nbsp;Android&amp;nbsp;앱에서&amp;nbsp;의존성&amp;nbsp;주입(DI)을&amp;nbsp;쉽게&amp;nbsp;구현할&amp;nbsp;수&amp;nbsp;있도록&amp;nbsp;도와주는&amp;nbsp;라이브러리입니다.&amp;nbsp;내부적으로는&amp;nbsp;Dagger를&amp;nbsp;기반으로&amp;nbsp;하며,&amp;nbsp;Android&amp;nbsp;컴포넌트와의&amp;nbsp;통합에&amp;nbsp;초점을&amp;nbsp;맞춰&amp;nbsp;개발자의&amp;nbsp;생산성을&amp;nbsp;높여줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;Android 개발에 특화된 DI 자동화&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;Android의 주요 컴포넌트에 DI 컨테이너를 자동 연결&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;컴포넌트의 수명 주기를 고려한 의존성 관리 지원&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;@HiltAndroidApp, @AndroidEntryPoint 등을 통한 간결한 DI 설정&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;DI 패턴을 표준화하여 팀 개발 및 테스트 코드 작성에 유리&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;Dagger 기반의 성능과 안정성&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;컴파일 타임에 의존성 그래프를 생성하여 런타임 성능이 뛰어남&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;컴파일 시 오류 검출로 안정성 향상&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;유연한 모듈화와 확장성 제공&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;Android&amp;nbsp;클래스&amp;nbsp;주입과&amp;nbsp;@AndroidEntryPoint&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;Hilt를 사용하는 앱은 @HiltAndroidApp이 적용된 Application 클래스를 반드시 포함해야 하며, 이 애노테이션을 통해 애플리케이션 전체에서 사용하는 최상위 DI 컨테이너가 생성되며, Application의 수명 주기에 맞춰 관리됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769439762857&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@HiltAndroidApp
class ExampleApplication : Application() { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;Hilt 사용 시 주요 특징 요약&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;@HiltAndroidApp으로 설정된 Application 클래스는 Hilt의 최상위 DI 컨테이너 역할을 한다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;Hilt는 @AndroidEntryPoint가 지정된 Android 클래스에 의존성 주입을 제공한다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;@AndroidEntryPoint는 다음 Android 컴포넌트에서 사용 가능하다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;DI를 사용하는 Android 컴포넌트에는 반드시 @AndroidEntryPoint를 붙여야 한다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;같은 생명주기를 공유하거나 해당 컴포넌트를 포함하는 상위 컴포넌트도 @AndroidEntryPoint로 지정되어야 한다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;Hilt는 컴파일 시 생성되는 코드를 통해 의존성을 주입하기 때문에 필드가 private으로 선언되어 있으면 접근할 수 없어 오류가 발생한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1769440703456&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@AndroidEntryPoint // Hilt의 주입 대상임을 @AndroidEntryPoint로 표시
class ExampleActivity : AppCompatActivity() {
    @Inject lateinit var analytics: AnalyticsAdapter // @Inject를 통해 주입
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;Hilt 바인딩 정의&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;Hilt에게 객체를 어떻게 생성해야 하는지를 알려주는 방법은 크게 두 가지입니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;생성자 주입(Constructor Injection)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;Hilt 모듈(Module)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;생성자&amp;nbsp;주입&amp;nbsp;(@Inject)&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;가장&amp;nbsp;간단하고&amp;nbsp;권장되는&amp;nbsp;방법은&amp;nbsp;클래스의&amp;nbsp;생성자에&amp;nbsp;@Inject&amp;nbsp;애노테이션을&amp;nbsp;부여하는&amp;nbsp;것입니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;이렇게&amp;nbsp;하면&amp;nbsp;Hilt는&amp;nbsp;필요한&amp;nbsp;의존성을&amp;nbsp;생성자에&amp;nbsp;자동으로&amp;nbsp;주입하고,&amp;nbsp;해당&amp;nbsp;클래스의&amp;nbsp;인스턴스를&amp;nbsp;생성할&amp;nbsp;수&amp;nbsp;있게&amp;nbsp;됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769441173483&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class AnalyticsAdapter @Inject constructor(
    private val service: AnalyticsService
) {
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;이&amp;nbsp;방식은&amp;nbsp;코드가&amp;nbsp;간결하고&amp;nbsp;명확하며,&amp;nbsp;주입&amp;nbsp;흐름을&amp;nbsp;이해하기도&amp;nbsp;쉬워&amp;nbsp;Hilt&amp;nbsp;사용&amp;nbsp;시&amp;nbsp;가장&amp;nbsp;먼저&amp;nbsp;고려할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;접근법입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;모듈&amp;nbsp;정의&amp;nbsp;(@Module)&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;외부&amp;nbsp;라이브러리나&amp;nbsp;인터페이스처럼,&amp;nbsp;개발자가&amp;nbsp;직접&amp;nbsp;생성자에&amp;nbsp;@Inject를&amp;nbsp;붙일&amp;nbsp;수&amp;nbsp;없는&amp;nbsp;경우에는&amp;nbsp;Hilt&amp;nbsp;모듈을&amp;nbsp;통해&amp;nbsp;의존성&amp;nbsp;생성을&amp;nbsp;정의할&amp;nbsp;수&amp;nbsp;있습니다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;Hilt 모듈을 정의할 때는 @Module 애노테이션과 함께 반드시 @InstallIn 애노테이션을 선언해야 합니다. @InstallIn은&amp;nbsp;해당&amp;nbsp;모듈이&amp;nbsp;어떤&amp;nbsp;Android&amp;nbsp;컴포넌트의&amp;nbsp;생명주기&amp;nbsp;범위(scope)&amp;nbsp;내에서&amp;nbsp;사용될지를&amp;nbsp;Hilt에&amp;nbsp;명시적으로&amp;nbsp;알려주는&amp;nbsp;역할을&amp;nbsp;합니다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;@Binds:&amp;nbsp;인터페이스&amp;nbsp;바인딩&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;인터페이스와 구현체 간의 바인딩을 설정할 때 사용합니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;예를&amp;nbsp;들어,&amp;nbsp;AnalyticsService라는&amp;nbsp;인터페이스에&amp;nbsp;대해&amp;nbsp;AnalyticsServiceImpl을&amp;nbsp;구현체로&amp;nbsp;사용하려면&amp;nbsp;다음과&amp;nbsp;같이&amp;nbsp;정의합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769441265601&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface AnalyticsService {
    fun analyticsMethods()
}

// Hilt가 AnalyticsServiceImpl의 인스턴스를 어떻게
// 만들어야 할지도 알아야 하기 때문에 생성자 주입이 필요하다.
class AnalyticsServiceImpl @Inject constructor(
    ...
) : AnalyticsService {
    override fun analyticsMethods() { ... }
}

@Module
@InstallIn(SingletonComponent::class)
abstract class AnalyticsModule {

    @Binds
    abstract fun bindAnalyticsService(
        impl: AnalyticsServiceImpl
    ): AnalyticsService
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;애노테이션이 지정된 함수는 Hilt에 다음 정보를 제공합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;함수 반환 타입은 함수가 어떤 인터페이스의 인스턴스를 제공하는지 Hilt에 알려준다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;함수 매개변수는 제공할 구현을 Hilt에 알려준다&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;@Provides:&amp;nbsp;외부&amp;nbsp;클래스&amp;nbsp;인스턴스&amp;nbsp;생성&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;생성자를 직접 제어할 수 없는 외부 클래스 (예: Retrofit, Room 등)의 경우, 인스턴스를 생성하는 로직을 함수 형태로 정의하여 제공합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769441887932&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

    @Provides
    fun provideRetrofit(): Retrofit {
        return Retrofit.Builder()
            .baseUrl(&quot;https://api.example.com&quot;)
            .build()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;주석이 달린 함수는 Hilt에 다음 정보를 제공합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;함수 반환 타입은 함수가 어떤 타입의 인스턴스를 제공하는지 Hilt에 알려준다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;함수 매개변수는 해당 타입의 종속 항목을 Hilt에 알려준다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;함수 본문은 해당 타입의 인스턴스를 제공하는 방법을 Hilt에 알려준다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt; Hilt는 해당 타입의 인스턴스를 제공해야 할 때마다 함수 본문을 실행한다&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div data-start-index=&quot;3785&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h4 data-start-index=&quot;3785&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;구성 요소의 수명 주기와 범위(Scope)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;div data-start-index=&quot;3811&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot; data-start-index=&quot;3811&quot;&gt;Hilt는 객체가 얼마나 오래 살아있어야 하는지를 안드로이드 구성 요소와 연결하여 자동으로 관리합니다. &lt;/span&gt;&lt;/div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 143px;&quot; border=&quot;1&quot; data-start-index=&quot;3868&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 24.6512%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #333333; --darkreader-inline-color: var(--darkreader-text-000000, #ffffff); background-color: #ffffff;&quot; data-darkreader-inline-color=&quot;&quot;&gt;&lt;b&gt; &lt;span style=&quot;text-align: start;&quot;&gt;생성된 구성 요소&lt;/span&gt; &lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 19.6512%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #333333; --darkreader-inline-color: var(--darkreader-text-000000, #ffffff); background-color: #ffffff;&quot; data-darkreader-inline-color=&quot;&quot;&gt;&lt;b&gt; &lt;span style=&quot;text-align: start;&quot;&gt;인젝터 대상&lt;/span&gt; &lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 23.2558%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #333333; --darkreader-inline-color: var(--darkreader-text-000000, #ffffff); background-color: #ffffff;&quot; data-darkreader-inline-color=&quot;&quot;&gt;&lt;b&gt; &lt;span style=&quot;text-align: start;&quot;&gt;범위 애노테이션&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 30.2326%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #333333; --darkreader-inline-color: var(--darkreader-text-000000, #ffffff); background-color: #ffffff;&quot; data-darkreader-inline-color=&quot;&quot;&gt;&lt;b&gt; &lt;span style=&quot;text-align: start;&quot;&gt;생성 및 소멸 시점&lt;/span&gt; &lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 24.6512%; height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;3898&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;SingletonComponent&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 19.6512%; height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;3916&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;Application&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 23.2558%; height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;3927&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;@Singleton&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 30.2326%; height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;3937&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot; data-start-index=&quot;3937&quot;&gt;앱 실행 시 생성 ~ 앱 종료 시 소멸&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 24.6512%; height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;3958&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;ActivityRetainedComponent&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 19.6512%; height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;3983&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot; data-start-index=&quot;3983&quot;&gt;N/A (ViewModel 지원)&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 23.2558%; height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;4001&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;@ActivityRetainedScoped&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 30.2326%; height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;4024&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot; data-start-index=&quot;4024&quot;&gt;구성 변경(화면 회전 등) 시에도 유지&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 24.6512%; height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;4045&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;ViewModelComponent&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 19.6512%; height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;4063&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;ViewModel&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 23.2558%; height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;4072&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;@ViewModelScoped&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 30.2326%; height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;4088&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot; data-start-index=&quot;4088&quot;&gt;ViewModel 생성 시 ~ 소멸 시&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 24.6512%; height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;4109&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;ActivityComponent&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 19.6512%; height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;4126&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;Activity&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 23.2558%; height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;4134&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;@ActivityScoped&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 30.2326%; height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;4149&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;onCreate()&amp;nbsp;생성 ~&amp;nbsp;onDestroy()&amp;nbsp;소멸&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 24.6512%; height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;4179&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;FragmentComponent&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 19.6512%; height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;4196&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;Fragment&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 23.2558%; height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;4204&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;@FragmentScoped&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 30.2326%; height: 21px;&quot;&gt;
&lt;div data-start-index=&quot;4219&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;onAttach()&amp;nbsp;생성 ~&amp;nbsp;onDestroy()&amp;nbsp;소멸&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 24.6512%; height: 17px;&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;ServiceComponent&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 19.6512%; height: 17px;&quot;&gt;
&lt;div data-start-index=&quot;4196&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;Service&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 23.2558%; height: 17px;&quot;&gt;
&lt;div data-start-index=&quot;4204&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;@ServiceScoped&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 30.2326%; height: 17px;&quot;&gt;
&lt;div data-start-index=&quot;4219&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;onCreate()&amp;nbsp;생성&amp;nbsp;~&amp;nbsp;onDestroy()&amp;nbsp;소멸&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;Hilt를 사용할 때, 의존성 객체는 기본적으로 범위(Scope)가 지정되지 않은 상태로 제공되며, 해당 객체가 필요할 때마다 항상 새로운 인스턴스가 생성됩니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;특정 구성요소 범위(Component Scope)를 지정함으로써, 같은 생명주기 내에서 객체를 재사용할 수 있도록 지원합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769443511809&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@ActivityScoped // Activity가 살아 있는 동안에는 항상 동일한 객체를 주입
class AnalyticsAdapter @Inject constructor(
  private val service: AnalyticsService
) { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;객체에 범위를 지정하면 메모리에 오래 유지되기 때문에, 무분별한 사용은 오히려 리소스 낭비로 이어질 수 있습니다. 따라서 구성요소 범위는 다음과 같은 경우에만 사용하는 것이 바람직합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;내부 상태를 유지해야 하는 객체&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;생성 비용이 큰 객체&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;스레드&amp;nbsp;동기화가&amp;nbsp;필요한&amp;nbsp;객체&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1769444062712&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Module
@InstallIn(SingletonComponent::class)
abstract class AnalyticsModule {

    @Singleton
    @Binds
    abstract fun bindAnalyticsService(
        analyticsServiceImpl: AnalyticsServiceImpl
    ): AnalyticsService
}

@Module
@InstallIn(SingletonComponent::class)
object AnalyticsModule {

  @Singleton
  @Provides
  fun provideAnalyticsService(): AnalyticsService {
      return Retrofit.Builder()
               .baseUrl(&quot;https://example.com&quot;)
               .build()
               .create(AnalyticsService::class.java)
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;구성요소 계층 구조&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;구성요소에 모듈을 설치하면 이 구성요소의 다른 바인딩 또는 구성요소 계층 구조에서 그 아래에 있는 하위 구성요소의 다른 바인딩의 종속 항목으로 설치된 모듈의 바인딩에 액세스할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;869&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEbIkT/dJMcaac2kpF/2isRWJjpsOw6Lt8AVaeVHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEbIkT/dJMcaac2kpF/2isRWJjpsOw6Lt8AVaeVHk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEbIkT/dJMcaac2kpF/2isRWJjpsOw6Lt8AVaeVHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEbIkT%2FdJMcaac2kpF%2F2isRWJjpsOw6Lt8AVaeVHk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;869&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;869&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;구성요소 기본 바인딩&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;각 Hilt 구성요소는 Hilt가 고유한 맞춤 바인딩에 종속 항목으로 삽입할 수 있는 기본 바인딩 세트와 함께 제공됩니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 151px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 16px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 16px;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;b&gt;Android&amp;nbsp;구성요소&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 16px;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;b&gt;기본 바인딩&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;SingletonComponent&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;Application&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;ActivityRetainedComponent&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;Application&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;ViewModelComponent&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;SavedStateHandle&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;ActivityComponent&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;Application,&amp;nbsp;Activity&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;FragmentComponent&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;Application,&amp;nbsp;Activity,&amp;nbsp;Fragment&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;ViewComponent&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;Application,&amp;nbsp;Activity,&amp;nbsp;View&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;ViewWithFragmentComponent&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;Application,&amp;nbsp;Activity,&amp;nbsp;Fragment,&amp;nbsp;View&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 16px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 16px;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;ServiceComponent&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 16px;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;Application,&amp;nbsp;Service&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre id=&quot;code_1769444702200&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class AnalyticsServiceImpl @Inject constructor(
  @ApplicationContext context: Context
) : AnalyticsService { ... }

class AnalyticsAdapter @Inject constructor(
  @ActivityContext context: Context
) { ... }

@HiltViewModel
class MyViewModel @Inject constructor(
    private val savedStateHandle: SavedStateHandle
) : ViewModel() { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;동일 타입의 여러 바인딩과 한정자(Qualifier)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;예를 들어, 로그용 OkHttpClient와 인증용 OkHttpClient가 각각 필요하다면 Hilt는 어떤 것을 주입해야 할지 혼란에 빠집니다. 이때 사용하는 '이름표'가 바로 한정자(Qualifier)입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;span style=&quot;text-align: start; --darkreader-inline-color: var(--darkreader-text-ffffff, #ffffff);&quot; data-darkreader-inline-color=&quot;&quot;&gt;@Binds 또는 @Provides 메서드에 주석을 지&lt;/span&gt;&lt;span style=&quot;text-align: start; --darkreader-inline-color: var(--darkreader-text-ffffff, #ffffff);&quot; data-darkreader-inline-color=&quot;&quot;&gt;정하는 데 사용할 한정자를 정의합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769442186802&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AuthInterceptorOkHttpClient

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class OtherInterceptorOkHttpClient&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;Hilt는 각 한정자와 일치하는 타입의 인스턴스를 제공하는 방법을 알아야 합니다. @Provides와 함께 Hilt 모듈을 사용하면 두 메서드 모두 동일한 반환 타입을 갖지만 한정자는 다음과 같이 두 가지의 서로 다른 바인딩으로 메서드에 라벨을 지정합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769442264353&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

  @AuthInterceptorOkHttpClient
  @Provides
  fun provideAuthInterceptorOkHttpClient(
    authInterceptor: AuthInterceptor
  ): OkHttpClient {
      return OkHttpClient.Builder()
               .addInterceptor(authInterceptor)
               .build()
  }

  @OtherInterceptorOkHttpClient
  @Provides
  fun provideOtherInterceptorOkHttpClient(
    otherInterceptor: OtherInterceptor
  ): OkHttpClient {
      return OkHttpClient.Builder()
               .addInterceptor(otherInterceptor)
               .build()
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;다음과&amp;nbsp;같이&amp;nbsp;필드&amp;nbsp;또는&amp;nbsp;매개변수에&amp;nbsp;해당&amp;nbsp;한정자로&amp;nbsp;주석을&amp;nbsp;지정하여&amp;nbsp;필요한&amp;nbsp;특정&amp;nbsp;유형을&amp;nbsp;삽입할&amp;nbsp;수&amp;nbsp;있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769442463871&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Module
@InstallIn(ActivityComponent::class)
object AnalyticsModule {

  @Provides
  fun provideAnalyticsService(
    @AuthInterceptorOkHttpClient okHttpClient: OkHttpClient
  ): AnalyticsService {
      return Retrofit.Builder()
               .baseUrl(&quot;https://example.com&quot;)
               .client(okHttpClient)
               .build()
               .create(AnalyticsService::class.java)
  }
}

class ExampleServiceImpl @Inject constructor(
  @AuthInterceptorOkHttpClient private val okHttpClient: OkHttpClient
) : ...

@AndroidEntryPoint
class ExampleActivity: AppCompatActivity() {

  @AuthInterceptorOkHttpClient
  @Inject lateinit var okHttpClient: OkHttpClient
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start; --darkreader-inline-color: var(--darkreader-text-ffffff, #ffffff); background-color: #ffffff;&quot; data-darkreader-inline-color=&quot;&quot;&gt;또한,&amp;nbsp;Hilt는&amp;nbsp;안드로이드&amp;nbsp;개발의&amp;nbsp;편의를&amp;nbsp;위해&amp;nbsp;@ApplicationContext와&amp;nbsp;@ActivityContext라는&amp;nbsp;사전&amp;nbsp;정의된&amp;nbsp;한정자를&amp;nbsp;기본으로&amp;nbsp;제공합니다. &lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769442500279&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class AnalyticsAdapter @Inject constructor(
    @ActivityContext private val context: Context,
    private val service: AnalyticsService
) { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Android</category>
      <author>어리둥절범고래</author>
      <guid isPermaLink="true">https://augustin26.tistory.com/134</guid>
      <comments>https://augustin26.tistory.com/entry/Hilt%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%9C-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85#entry134comment</comments>
      <pubDate>Tue, 27 Jan 2026 01:27:51 +0900</pubDate>
    </item>
    <item>
      <title>Android의 의존성 주입</title>
      <link>https://augustin26.tistory.com/entry/Android%EC%9D%98-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;Android 권장 아키텍처는 관심사 분리를 위해 코드를 역할별 클래스로 나누고, 이 과정에서 여러 작은 클래스들이 서로 의존성으로 연결됩니다. 이런 관계는 화살표로 표현한 의존성 그래프로 나타낼 수 있으며, 의존성 주입(DI)을 사용하면 의존성 연결과 테스트용 구현(가짜/모의 객체) 교체가 쉬워집니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rjRQv/dJMcahpG9ET/dOKURpqNj4DqqahL4cNEBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rjRQv/dJMcahpG9ET/dOKURpqNj4DqqahL4cNEBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rjRQv/dJMcahpG9ET/dOKURpqNj4DqqahL4cNEBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrjRQv%2FdJMcahpG9ET%2FdOKURpqNj4DqqahL4cNEBK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;752&quot; height=&quot;564&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;의존성 주입(DI)은 프로그래밍에 널리 사용되는 기법으로, 의존성 주입을 구현하면 다음과 같은 이점을 누릴 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;코드&amp;nbsp;재사용&amp;nbsp;가능&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;리팩터링&amp;nbsp;편의성&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;테스트&amp;nbsp;편의성&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;의존성&amp;nbsp;주입(Dependency Injection, DI)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;클래스가 스스로 의존성을 만들거나 찾지 않고, 필요한 것을 주입받아 사용하는 방식입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;574&quot; data-origin-height=&quot;546&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blE9VO/dJMcaajM2oB/INP1wlyAkXl4kzjRrgVVQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blE9VO/dJMcaajM2oB/INP1wlyAkXl4kzjRrgVVQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blE9VO/dJMcaajM2oB/INP1wlyAkXl4kzjRrgVVQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblE9VO%2FdJMcaajM2oB%2FINP1wlyAkXl4kzjRrgVVQK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;417&quot; height=&quot;397&quot; data-origin-width=&quot;574&quot; data-origin-height=&quot;546&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;DI을 실행하는 두 가지 주요 방법은 다음과 같습니다. &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;생성자 삽입&lt;/b&gt;: 클래스의 의존성을 생성자에 전달&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;필드 삽입(또는 setter 삽입)&lt;/b&gt;: 의존성은 클래스가 생성된 후 인스턴스화&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769346223460&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 생성자 삽입
class Car(private val engine: Engine) {
    fun start() {
        engine.start()
    }
}

fun main(args: Array) {
    val engine = Engine()
    val car = Car(engine)
    car.start()
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1769346199398&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 필드 삽입
class Car {
    lateinit var engine: Engine

    fun start() {
        engine.start()
    }
}

fun main(args: Array) {
    val car = Car()
    car.engine = Engine()
    car.start()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;자동화된 의존성 주입&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;수동 DI는 라이브러리 없이 개발자가 의존성을 직접 생성, 전달, 관리하는 방식으로 다음과 같은 문제가 존재합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;앱 규모가 커질수록 상용구 코드가 급증&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;다층 구조 아키텍처에서는 상위 객체를 만들기 위해 하위 레이어의 모든 의존성을 계속 전달해야 함&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;의존성의 생명주기를&amp;nbsp;관리하는&amp;nbsp;컨테이너(그래프)를&amp;nbsp;직접&amp;nbsp;유지해야&amp;nbsp;함&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;이&amp;nbsp;문제를&amp;nbsp;해결하기&amp;nbsp;위해&amp;nbsp;의존성을&amp;nbsp;생성하고&amp;nbsp;제공하는&amp;nbsp;과정을&amp;nbsp;자동화해주는&amp;nbsp;라이브러리들이&amp;nbsp;있으며,&amp;nbsp;크게&amp;nbsp;두&amp;nbsp;부류로&amp;nbsp;나뉩니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;리플렉션&amp;nbsp;기반&lt;/b&gt;: 런타임에 의존성을 연결&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;정적 방식&lt;/b&gt;: 컴파일 타임에 의존성 연결 코드를 생성&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;의존성 주입의 대안&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;서비스 로케이터&lt;/b&gt;는 DI의 대안으로, 의존성을 미리 생성&amp;middot;보관해두고 필요할 때 클래스에 제공해 구현체와의 결합을 줄이는 패턴입니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769346940638&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;object ServiceLocator {
    fun getEngine(): Engine = Engine()
}

class Car {
    private val engine = ServiceLocator.getEngine()

    fun start() {
        engine.start()
    }
}

fun main(args: Array) {
    val car = Car()
    car.start()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #ffffff; text-align: start;&quot; data-darkreader-inline-color=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;DI는 의존성을 주입받는 방식이라 의존성이 명시적으로 드러나는 편이고, 서비스 로케이터는 의존성을 조회해 가져오는 방식이라 편리할 수 있지만 의존성이 숨겨져 안티패턴으로 보기도 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #ffffff; text-align: start;&quot; data-darkreader-inline-color=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #ffffff; text-align: start;&quot; data-darkreader-inline-color=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-darkreader-inline-bgcolor=&quot;&quot; data-darkreader-inline-color=&quot;&quot;&gt;DI와 비교&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;테스트가&amp;nbsp;어려움&lt;/b&gt;:&amp;nbsp;전역&amp;nbsp;로케이터를&amp;nbsp;여러&amp;nbsp;테스트가&amp;nbsp;공유해&amp;nbsp;서로&amp;nbsp;영향을&amp;nbsp;줄&amp;nbsp;수&amp;nbsp;있음&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;의존성이 숨겨짐&lt;/b&gt;: 생성자/파라미터에 드러나지 않아 클래스가 무엇을 필요로 하는지 파악이 어려움&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;런타임 오류 위험&lt;/b&gt;: 로케이터/의존성 구성이 바뀌면 참조가 깨져 실행 중 오류나 테스트 실패로 이어질 수 있음&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;스코프 관리가 어려움&lt;/b&gt;: 앱 전체가 아닌 특정 화면/흐름 등 &amp;ldquo;일부 기간&amp;rdquo;만 살아야 하는 객체의 생명주기 관리가 복잡함&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;수동 의존성 주입&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;다음과 같은 그래프의 의존성을 수동 주입을 통해 직접 구현해보도록 하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;545&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOZ8XO/dJMcajnr4L2/Om2hRFiPsfzkBujgaHZ4RK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOZ8XO/dJMcajnr4L2/Om2hRFiPsfzkBujgaHZ4RK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOZ8XO/dJMcajnr4L2/Om2hRFiPsfzkBujgaHZ4RK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOZ8XO%2FdJMcajnr4L2%2FOm2hRFiPsfzkBujgaHZ4RK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;674&quot; height=&quot;287&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;545&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;그래프의 Repository 및 DataSource 클래스는 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769348642356&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class UserRepository(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) { ... }

class UserLocalDataSource { ... }
class UserRemoteDataSource(
    private val loginService: LoginRetrofitService // Retrofit
) { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;LoginActivity가&amp;nbsp;LoginViewModel을&amp;nbsp;만들기&amp;nbsp;위해&amp;nbsp;필요한&amp;nbsp;의존성들을&amp;nbsp;직접&amp;nbsp;생성해서&amp;nbsp;차례대로&amp;nbsp;연결합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769350846891&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class LoginActivity : Activity() {

    private lateinit var loginViewModel: LoginViewModel

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

        // LoginViewModel이 필요로 하는 의존성들을 수동으로 생성/연결하는 예시

        // 1) 네트워크 API(LoginService) 생성
        val retrofit = Retrofit.Builder()
            .baseUrl(&quot;https://example.com&quot;)
            .build()
            .create(LoginService::class.java)

        // 2) Repository가 필요로 하는 DataSource 생성
        val remoteDataSource = UserRemoteDataSource(retrofit) // 원격 데이터 소스(서버)
        val localDataSource = UserLocalDataSource()           // 로컬 데이터 소스

        // 3) Repository 생성 (local + remote 조합)
        val userRepository = UserRepository(localDataSource, remoteDataSource)

        // 4) ViewModel 생성 (Repository 주입)
        loginViewModel = LoginViewModel(userRepository)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;이&amp;nbsp;접근&amp;nbsp;방식은&amp;nbsp;다음과&amp;nbsp;같은&amp;nbsp;문제가&amp;nbsp;있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;상용구 코드가 많고 중복됨&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;생성&amp;nbsp;순서가&amp;nbsp;강제됨&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;재사용과 테스트가 어려워짐&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;컨테이너로 의존성 관리&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;객체 재사용 문제를 해결하려면 의존성을 가져오는 데 사용하는 자체 의존성 컨테이너 클래스를 만들면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769351077782&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 앱 전체에서 공유할 객체(의존성)들을 담는 컨테이너
class AppContainer {

    // UserRepository가 필요로 하는 의존성들을 컨테이너 안에서 한 번만 생성
    private val retrofit = Retrofit.Builder()
        .baseUrl(&quot;https://example.com&quot;)
        .build()
        .create(LoginService::class.java)

    private val remoteDataSource = UserRemoteDataSource(retrofit)
    private val localDataSource = UserLocalDataSource()

    // 외부에서 사용할 의존성만 공개(예: userRepository)
    val userRepository = UserRepository(localDataSource, remoteDataSource)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;Application은 앱 프로세스 동안 살아있는 객체로, 싱글톤 패턴을 쓰는 게 아니라&amp;nbsp;Application이&amp;nbsp;AppContainer&amp;nbsp;인스턴스&amp;nbsp;하나를&amp;nbsp;들고&amp;nbsp;있는&amp;nbsp;구조입니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769351095207&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// AndroidManifest.xml에 등록해야 하는 커스텀 Application
class MyApplication : Application() {
    // 앱 전체에서 하나만 만들어서 모든 Activity가 공유
    val appContainer = AppContainer()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;Activity는 더 이상 Retrofit &amp;rarr; DataSource &amp;rarr; Repository를 직접 만들지 않고, 컨테이너에서 이미 만들어진 userRepository를 꺼내 쓰기만 합니다. 결과적으로 중복 코드가 줄고, UserRepository도 앱 전체에서 재사용할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769351508852&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class LoginActivity: Activity() {

    private lateinit var loginViewModel: LoginViewModel

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

        // 애플리케이션의 AppContainer 인스턴스에서 userRepository를 가져옵니다
        val appContainer = (application as MyApplication).appContainer
        loginViewModel = LoginViewModel(appContainer.userRepository)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;UserRepository를&amp;nbsp;싱글톤으로&amp;nbsp;만들기보다,&amp;nbsp;앱&amp;nbsp;전역의&amp;nbsp;AppContainer가&amp;nbsp;필요한&amp;nbsp;객체를&amp;nbsp;만들어&amp;nbsp;공유합니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;LoginViewModel을&amp;nbsp;여러&amp;nbsp;곳에서&amp;nbsp;쓰게&amp;nbsp;되면&amp;nbsp;생성&amp;nbsp;방식을&amp;nbsp;한&amp;nbsp;곳에&amp;nbsp;모으기&amp;nbsp;위해&amp;nbsp;Factory로&amp;nbsp;ViewModel을&amp;nbsp;생성합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769351291282&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 특정 타입의 객체를 만드는 팩토리 인터페이스
interface Factory&amp;lt;T&amp;gt; {
    fun create(): T
}

// LoginViewModel을 생성하는 팩토리
// LoginViewModel은 UserRepository가 필요하므로, 팩토리가 UserRepository를 들고 있다가 생성 시 주입해준다.
class LoginViewModelFactory(private val userRepository: UserRepository) : Factory&amp;lt;LoginViewModel&amp;gt; {
    override fun create(): LoginViewModel {
        return LoginViewModel(userRepository)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;AppContainer&lt;span style=&quot;text-align: start;&quot; data-darkreader-inline-bgcolor=&quot;&quot; data-darkreader-inline-color=&quot;&quot;&gt;에&amp;nbsp;&lt;/span&gt;LoginViewModelFactory&lt;span style=&quot;text-align: start;&quot; data-darkreader-inline-bgcolor=&quot;&quot; data-darkreader-inline-color=&quot;&quot;&gt;를 포함하여&amp;nbsp;&lt;/span&gt;LoginActivity&lt;span style=&quot;text-align: start;&quot; data-darkreader-inline-bgcolor=&quot;&quot; data-darkreader-inline-color=&quot;&quot;&gt;에서 사용하게 할 수 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769351685433&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class AppContainer {
    // (이미 만들어 둔 의존성들)
    val userRepository = UserRepository(localDataSource, remoteDataSource)

    // ViewModel을 만들 &amp;ldquo;공식 경로&amp;rdquo;
    val loginViewModelFactory = LoginViewModelFactory(userRepository)
}

class LoginActivity : Activity() {

    private lateinit var loginViewModel: LoginViewModel

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

        val appContainer = (application as MyApplication).appContainer
        loginViewModel = appContainer.loginViewModelFactory.create()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;여전히&amp;nbsp;다음과&amp;nbsp;같은&amp;nbsp;문제를&amp;nbsp;고려해야&amp;nbsp;합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;여전히 개발자가 AppContainer를 직접 설계/관리하면서 모든 의존성을 수동으로 생성&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;여전히 상용구 코드가 많으며, 객체의 재사용 여부에 따라 수동으로 팩토리나 매개변수를 만들어야 함&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;b&gt;애플리케이션 흐름에서 의존성 관리&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;흐름이&amp;nbsp;다양하면&amp;nbsp;객체가&amp;nbsp;해당&amp;nbsp;흐름의&amp;nbsp;범위에만&amp;nbsp;있기를&amp;nbsp;원할&amp;nbsp;수&amp;nbsp;있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;흐름에&amp;nbsp;따라&amp;nbsp;필요하지&amp;nbsp;않은&amp;nbsp;인스턴스를&amp;nbsp;삭제해야&amp;nbsp;합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;AppContainer&amp;nbsp;내에&amp;nbsp;FlowContainer&amp;nbsp;객체를&amp;nbsp;만들면&amp;nbsp;가능합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;240&quot; data-start=&quot;173&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;AppContainer: 앱 전체 수명(프로세스) 동안 재사용할 것들([UserRepository)&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;329&quot; data-start=&quot;241&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;LoginContainer: 로그인 흐름 동안만 살아야 하는 것들(LoginUserData, 로그인용 ViewModel/Factory)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1769352521458&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// FlowContainer
class LoginContainer(val userRepository: UserRepository) {

    // 로그인 흐름 동안 공유할 데이터(새 흐름이면 새 인스턴스)
    val loginData = LoginUserData()

    // 로그인 흐름에서 쓸 ViewModel 생성 도구
    val loginViewModelFactory = LoginViewModelFactory(userRepository)
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1769351931018&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class AppContainer {
    val userRepository = UserRepository(localDataSource, remoteDataSource)

    // 로그인 중이 아닐 땐 null
    var loginContainer: LoginContainer? = null
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1769351959755&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class LoginActivity: Activity() {

    private lateinit var loginViewModel: LoginViewModel
    private lateinit var loginData: LoginUserData
    private lateinit var appContainer: AppContainer


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        appContainer = (application as MyApplication).appContainer

        // 로그인 흐름 시작 &amp;rarr; LoginContainer 생성해서 AppContainer에 넣음
        appContainer.loginContainer = LoginContainer(appContainer.userRepository)

        // 흐름 컨테이너에서 필요한 것 꺼내 사용
        loginViewModel = appContainer.loginContainer.loginViewModelFactory.create()
        loginData = appContainer.loginContainer.loginData
    }

    override fun onDestroy() {
        // 로그인 흐름 종료 &amp;rarr; LoginContainer 제거(메모리 해제 목적)
        appContainer.loginContainer = null
        super.onDestroy()
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Android</category>
      <author>어리둥절범고래</author>
      <guid isPermaLink="true">https://augustin26.tistory.com/133</guid>
      <comments>https://augustin26.tistory.com/entry/Android%EC%9D%98-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85#entry133comment</comments>
      <pubDate>Sun, 25 Jan 2026 23:54:47 +0900</pubDate>
    </item>
  </channel>
</rss>