DEV Community

dss99911
dss99911

Posted on • Originally published at dss99911.github.io

Android 의존성 주입 (DI) 가이드

Android 의존성 주입 (DI) 가이드

의존성 주입(Dependency Injection)은 코드의 테스트 용이성과 유지보수성을 높이는 디자인 패턴입니다.

왜 의존성 주입이 필요한가?

DI의 장점

  1. 테스트 용이성: Mock 객체로 쉽게 대체 가능
  2. 코드 재사용성: 모듈 단위로 교체 가능
  3. 유지보수성: 의존성이 명확하게 드러남
  4. 결합도 감소: 컴포넌트 간 느슨한 결합

DI 없이

class UserRepository {
    private val api = ApiService.create()  // 직접 생성
    private val database = AppDatabase.getInstance()  // 직접 참조
}
Enter fullscreen mode Exit fullscreen mode

DI 사용

class UserRepository(
    private val api: ApiService,  // 주입받음
    private val database: AppDatabase  // 주입받음
)
Enter fullscreen mode Exit fullscreen mode

Dagger2

기본 개념

  • Compile-time DI: 런타임이 아닌 컴파일 시점에 의존성 그래프 생성
  • @Inject: 주입받을 필드나 생성자에 표시
  • @Module: 의존성을 제공하는 클래스
  • @Provides: 의존성을 제공하는 메서드
  • @Component: 의존성 그래프의 진입점

의존성 추가

dependencies {
    implementation("com.google.dagger:dagger:2.48")
    kapt("com.google.dagger:dagger-compiler:2.48")
}
Enter fullscreen mode Exit fullscreen mode

@Inject 사용

class CoffeeMaker @Inject constructor(
    private val heater: Heater,
    private val pump: Pump
) {
    fun brew() { }
}
Enter fullscreen mode Exit fullscreen mode

@Module과 @Provides

@Module
class AppModule {
    @Provides
    @Singleton
    fun provideApiService(): ApiService {
        return Retrofit.Builder()
            .baseUrl("https://api.example.com/")
            .build()
            .create(ApiService::class.java)
    }

    @Provides
    fun provideUserRepository(
        api: ApiService,
        db: AppDatabase
    ): UserRepository {
        return UserRepository(api, db)
    }
}
Enter fullscreen mode Exit fullscreen mode

@Component

@Singleton
@Component(modules = [AppModule::class, NetworkModule::class])
interface AppComponent {
    fun inject(activity: MainActivity)
    fun userRepository(): UserRepository
}
Enter fullscreen mode Exit fullscreen mode

Component 생성 및 사용

// Application에서 초기화
class MyApp : Application() {
    lateinit var appComponent: AppComponent

    override fun onCreate() {
        super.onCreate()
        appComponent = DaggerAppComponent.builder()
            .appModule(AppModule())
            .build()
    }
}

// Activity에서 사용
class MainActivity : AppCompatActivity() {
    @Inject
    lateinit var userRepository: UserRepository

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        (application as MyApp).appComponent.inject(this)
    }
}
Enter fullscreen mode Exit fullscreen mode

Scope

@Singleton  // 항상 같은 인스턴스
@Provides
fun provideDatabase(): AppDatabase { }

@Reusable  // 재사용하지만 싱글톤은 아님
@Provides
fun provideHelper(): Helper { }

// 커스텀 스코프
@Scope
@Retention(AnnotationRetention.RUNTIME)
annotation class ActivityScope
Enter fullscreen mode Exit fullscreen mode

Qualifier

같은 타입의 다른 인스턴스 구분:

@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class AuthInterceptor

@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class LoggingInterceptor

@Module
class NetworkModule {
    @Provides
    @AuthInterceptor
    fun provideAuthInterceptor(): Interceptor = AuthInterceptor()

    @Provides
    @LoggingInterceptor
    fun provideLoggingInterceptor(): Interceptor = HttpLoggingInterceptor()
}
Enter fullscreen mode Exit fullscreen mode

Koin

Koin은 Kotlin을 위한 경량 DI 프레임워크입니다.

장점

  • 간단한 DSL
  • 빠른 학습 곡선
  • 코드 생성 없음 (리플렉션 사용)

의존성 추가

dependencies {
    implementation("io.insert-koin:koin-android:3.5.0")
}
Enter fullscreen mode Exit fullscreen mode

모듈 정의

val appModule = module {
    single { ApiService.create() }
    single { AppDatabase.getInstance(get()) }
    single { UserRepository(get(), get()) }
    viewModel { UserViewModel(get()) }
}
Enter fullscreen mode Exit fullscreen mode

초기화

class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        startKoin {
            androidContext(this@MyApp)
            modules(appModule)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

사용

class MainActivity : AppCompatActivity() {
    // ViewModel 주입
    private val viewModel: UserViewModel by viewModel()

    // 일반 의존성 주입
    private val repository: UserRepository by inject()
}
Enter fullscreen mode Exit fullscreen mode

Hilt

Hilt는 Dagger 위에 구축된 Android 전용 DI 라이브러리입니다.

의존성 추가

// project build.gradle
plugins {
    id("com.google.dagger.hilt.android") version "2.48" apply false
}

// app build.gradle
plugins {
    id("kotlin-kapt")
    id("com.google.dagger.hilt.android")
}

dependencies {
    implementation("com.google.dagger:hilt-android:2.48")
    kapt("com.google.dagger:hilt-compiler:2.48")
}
Enter fullscreen mode Exit fullscreen mode

Application 설정

@HiltAndroidApp
class MyApp : Application()
Enter fullscreen mode Exit fullscreen mode

Activity/Fragment 주입

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    @Inject
    lateinit var analytics: Analytics

    private val viewModel: UserViewModel by viewModels()
}

@AndroidEntryPoint
class UserFragment : Fragment() {
    @Inject
    lateinit var repository: UserRepository
}
Enter fullscreen mode Exit fullscreen mode

모듈 정의

@Module
@InstallIn(SingletonComponent::class)
object AppModule {
    @Provides
    @Singleton
    fun provideApiService(): ApiService {
        return Retrofit.Builder()
            .baseUrl("https://api.example.com/")
            .build()
            .create(ApiService::class.java)
    }
}

@Module
@InstallIn(ViewModelComponent::class)
object ViewModelModule {
    @Provides
    fun provideUserRepository(api: ApiService): UserRepository {
        return UserRepository(api)
    }
}
Enter fullscreen mode Exit fullscreen mode

ViewModel 주입

@HiltViewModel
class UserViewModel @Inject constructor(
    private val repository: UserRepository
) : ViewModel() {
    // ...
}
Enter fullscreen mode Exit fullscreen mode

Hilt 컴포넌트

컴포넌트 스코프 생성 시점
SingletonComponent @singleton Application#onCreate()
ActivityComponent @ActivityScoped Activity#onCreate()
ViewModelComponent @ViewModelScoped ViewModel 생성
FragmentComponent @FragmentScoped Fragment#onAttach()

비교

특징 Dagger2 Koin Hilt
학습 곡선 높음 낮음 중간
성능 빠름 (컴파일 타임) 느림 (런타임 리플렉션) 빠름
Android 통합 수동 쉬움 자동
에러 발견 컴파일 타임 런타임 컴파일 타임
코드량 많음 적음 중간

결론

  • 소규모 프로젝트: Koin (간단하고 빠른 설정)
  • 대규모 프로젝트: Hilt (컴파일 타임 검증, Android 최적화)
  • 기존 Dagger 사용 프로젝트: Hilt로 마이그레이션 고려

의존성 주입을 통해 테스트 가능하고 유지보수하기 쉬운 코드를 작성하세요.


Originally published at https://dss99911.github.io

Top comments (0)