--- id: android-hilt-di-patterns title: Android Hilt — DI 모듈과 스코프 category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [android, hilt, di, dagger, vibe-coding] tech_stack: { language: "Kotlin / Hilt", applicable_to: ["Android"] } applied_in: [] aliases: [@HiltAndroidApp, @Module, @Provides, ViewModelComponent] --- # Android Hilt — DI > Dagger 의 Android 친화 wrapper. **컴포넌트 = 스코프**. ApplicationComponent (Singleton), ActivityComponent, FragmentComponent, ViewModelComponent. 잘못된 스코프 = leak 또는 잘못된 인스턴스 공유. ## 📖 핵심 개념 - @HiltAndroidApp: Application 클래스에. 부팅. - @AndroidEntryPoint: Activity / Fragment / View / Service 에. - @HiltViewModel: ViewModel 자동 주입. - @Module + @InstallIn: 어떤 컴포넌트에 binding. ## 💻 코드 패턴 ### 부팅 ```kotlin @HiltAndroidApp class App : Application() // AndroidManifest.xml — name=".App" ``` ### Module — 외부 라이브러리 binding ```kotlin @Module @InstallIn(SingletonComponent::class) object NetworkModule { @Provides @Singleton fun retrofit(): Retrofit = Retrofit.Builder() .baseUrl("https://api.example.com/") .addConverterFactory(MoshiConverterFactory.create()) .build() @Provides @Singleton fun userApi(retrofit: Retrofit): UserApi = retrofit.create(UserApi::class.java) } @Module @InstallIn(SingletonComponent::class) abstract class RepoModule { @Binds @Singleton abstract fun bindUserRepo(impl: UserRepoImpl): UserRepo } ``` ### Scoped repository ```kotlin @Singleton class UserRepo @Inject constructor( private val api: UserApi, private val dao: UserDao, ) { ... } ``` ### ViewModel 주입 ```kotlin @HiltViewModel class ProfileViewModel @Inject constructor( private val repo: UserRepo, private val savedState: SavedStateHandle, ) : ViewModel() { ... } ``` ### Compose ```kotlin @Composable fun ProfileScreen(viewModel: ProfileViewModel = hiltViewModel()) { ... } ``` ### Worker 주입 ```kotlin @HiltWorker class SyncWorker @AssistedInject constructor( @Assisted ctx: Context, @Assisted params: WorkerParameters, private val repo: SyncRepo, ) : CoroutineWorker(ctx, params) { ... } // App 에서 @HiltAndroidApp class App : Application(), Configuration.Provider { @Inject lateinit var workerFactory: HiltWorkerFactory override val workManagerConfiguration: Configuration get() = Configuration.Builder().setWorkerFactory(workerFactory).build() } ``` ### Qualifier — 같은 타입 다른 인스턴스 ```kotlin @Qualifier annotation class Authed @Qualifier annotation class Public @Provides @Singleton @Authed fun authedClient(): OkHttpClient = OkHttpClient.Builder().addInterceptor(AuthInterceptor()).build() @Provides @Singleton @Public fun publicClient(): OkHttpClient = OkHttpClient() class Repo @Inject constructor(@Authed private val client: OkHttpClient) { ... } ``` ## 🤔 의사결정 기준 | 인스턴스 lifecycle | 스코프 | |---|---| | 앱 전체 (DB, network client, repo) | @Singleton in SingletonComponent | | Activity 동안 (navigation graph) | @ActivityRetainedScoped | | ViewModel 동안 | @ViewModelScoped | | Fragment 동안 | @FragmentScoped | | 매번 새로 | scope 없음 (default) | ## ❌ 안티패턴 - **모든 곳 @Singleton**: 큰 객체 메모리 영구 점유. 필요한 곳만. - **Activity scope 인데 ViewModel 에 주입**: ViewModel 이 Activity 보다 오래 → leak. - **Context 잘못된 종류**: ApplicationContext vs ActivityContext. 가장 작은 scope. - **모듈을 잘못된 컴포넌트에 InstallIn**: 의존성 못 찾음. - **@Provides 와 @Binds 혼용 + 같은 타입**: ambiguous. - **테스트 환경에서 production module 그대로**: 외부 의존. @TestInstallIn 으로 fake. - **ViewModel constructor 에 Context 주입**: leak. @ApplicationContext 만. ## 🤖 LLM 활용 힌트 - 신규 Android = Hilt 디폴트. - Singleton vs ViewModelScoped 명확히. - Test 는 hiltRules + fake module. ## 🔗 관련 문서 - [[Android_Room_Patterns]] - [[Android_WorkManager_Patterns]]