Notice
Recent Posts
Recent Comments
Link
Tags
more
관리 메뉴

주완 님의 블로그

DI - koin & Hilt 본문

Android

DI - koin & Hilt

vvan2 2025. 5. 25. 19:10

Dependency Injection (DI)의 개요 및 필요성

  • *DI(Dependency Injection, 의존성 주입)**는 객체 간의 결합도를 낮추고, 유연하고 확장 가능한 애플리케이션 구조를 구현하기 위한 설계 패턴입니다.

클래스 간 강한 결합(Strong Coupling)은 코드의 재사용성과 유지보수성을 떨어뜨리며, 하나의 클래스가 변경될 경우 연쇄적으로 관련된 모든 클래스에 영향을 미칠 수 있습니다. 이를 해결하기 위해 의존 객체를 외부에서 주입(Inject)받는 방식으로 설계를 분리하며, 이러한 방식을 가능하게 해주는 외부 컨테이너를 DI 컨테이너라고 합니다.

Android의 종속 항목 삽입  |  App architecture  |  Android Developers

 

Android의 종속 항목 삽입  |  App architecture  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. Android의 종속 항목 삽입 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 종속 항목 삽입(DI)은 프로그래

developer.android.com

 

 

DI 사용의 주요 목적

  • 클래스 재사용 가능 및 종속 항목 분리 , 리팩터링 편의성, 테스트 편의성 으로 말합니다
  1. 클래스 재사용 및 종속 항목 분리
    • 구현체 교체가 용이하며, 클래스가 직접 의존 객체를 생성하지 않기 때문에 다양한 환경에서 재사용 가능합니다.
  2. 리팩터링의 용이성
    • 의존성 주입을 통해 객체 구성 시점에 종속성을 확인할 수 있어 구현 세부사항이 드러나지 않으며, 안정적인 구조를 유지할 수 있습니다.
  3. 테스트 편의성 향상
    • 테스트 환경에서 목(Mock) 객체 등 다양한 구현체를 주입하여 독립적인 테스트가 가능합니다.

Koin: Kotlin 기반의 경량 DI 프레임워크

Koin은 Kotlin에 최적화된 DSL 기반 DI 프레임워크로, Android 개발 환경에서 간편하게 사용할 수 있도록 설계되었습니다.

Koin의 특징

  • Kotlin DSL 사용
  • 런타임 의존성 주입 방식
  • Android Jetpack(ViewModel 등)과의 높은 호환성
  • 러닝커브가 낮아 빠르게 적용 가능
  • 어노테이션이 불필요하여 컴파일 시간이 짧음

단점

  • 런타임 시점에 의존성 해석 → 성능 저하 가능
  • 리플렉션 사용으로 인한 성능 이슈
  • koin.get() 등 직접 호출 시 모듈 간 의존성 관리가 어려워 멀티모듈 구조에 불리

💡

*리플렉션(Reflection)**은 프로그램이 실행 중(run-time)에 객체의 클래스, 메서드, 필드 등의 정보를 조회하고 조작할 수 있게 해주는 메커니즘입니다.

 

 

Koin 사용 예시

Gradle 설정

kotlin
복사편집
// build.gradle
implementation "io.insert-koin:koin-core:3.2.0"
implementation "io.insert-koin:koin-android:3.2.0"
implementation "io.insert-koin:koin-android-compat:3.2.0"
implementation "io.insert-koin:koin-androidx-workmanager:3.2.0"
implementation "io.insert-koin:koin-androidx-navigation:3.2.0"

Application 클래스 설정

kotlin
복사편집
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        startKoin {
            androidContext(this@MyApplication)
            androidLogger()
            modules(appModule)
        }
    }
}

 

AndroidManifest.xml 설정:

xml
복사편집
<applicationandroid:name=".MyApplication"
    ... >

Module 정의

kotlin
복사편집
val appModule = module {
    single { Animal() }
    factory { Cat(get()) }
    viewModel { MainViewModel() }
}

의존성 주입

kotlin
복사편집
class MainFragment : Fragment() {

    private val viewModel: MainViewModel by viewModel()
    private val cat2: Cat by inject()

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

        val cat = get<Cat>()
        cat.speek()
        cat2.speek()
        viewModel.doSomething()
    }
}

 

 

 

import org.koin.android.ext.android.get
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.viewModel

class MainFragment : Fragment() {
    companion object {
        fun newInstance() = MainFragment()
    }

    private val viewModel: MainViewModel by viewModel()

    private val cat2: Cat by inject()

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        return inflater.inflate(R.layout.fragment_main, container, false)
    }

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

        Log.w("MainFragment", "cat =================================================")
        val cat = get<Cat>()
        cat.speek()

        Log.w("MainFragment", "cat2 =================================================")
        cat2.speek()

        Log.w("MainFragment", "viewModel =================================================")
        viewModel.doSomething()
    }
}


Hilt: Android 공식 DI 프레임워크

Hilt는 Google이 제공하는 Android 전용 DI 프레임워크로, Dagger 기반에서 구축되어 Android 컴포넌트 통합 및 수명주기 관리 기능을 제공합니다.

Hilt의 장점

  • 컴파일 타임에 의존성 그래프 생성 → 빠른 실행 성능
  • Android 컴포넌트(Activity, Fragment 등)에서 손쉬운 주입
  • Dagger 기반의 안정성과 유연성을 간편한 API로 제공
  • Jetpack 및 Android 아키텍처 컴포넌트와 강력하게 통합됨

단점

  • 학습 곡선이 있으며, 어노테이션 기반으로 구조에 대한 명확한 이해가 필요

1. Gradle 설정

groovy
복사편집
// project-level build.gradle
buildscript {
    dependencies {
        classpath "com.google.dagger:hilt-android-gradle-plugin:2.48"
    }
}

// app-level build.gradle
plugins {
    id 'kotlin-kapt'
    id 'dagger.hilt.android.plugin'
}

dependencies {
    implementation "com.google.dagger:hilt-android:2.48"
    kapt "com.google.dagger:hilt-compiler:2.48"
}


2. Application 클래스에 Hilt 적용

kotlin
복사편집
@HiltAndroidApp
class MyApplication : Application()

@HiltAndroidApp 어노테이션을 통해 Hilt DI 컨테이너를 애플리케이션 전체에 적용합니다.


3. 의존성 제공 모듈 생성

kotlin
복사편집
@Module
@InstallIn(SingletonComponent::class)
object AppModule {

    @Provides
    @Singleton
    fun provideAnimal(): Animal {
        return Animal()
    }
}

@Module + @InstallIn으로 Hilt에 의존성 제공 모듈을 등록하며, @Provides로 구체 인스턴스를 반환합니다.


4. 의존성 사용 클래스 정의

kotlin
복사편집
class Cat @Inject constructor(private val animal: Animal) {
    fun speek() {
        Log.d("Cat", "speek")
        animal.speek("Cat")
    }
}

@Inject 생성자를 통해 의존 객체가 자동으로 주입됩니다.


5. ViewModel에 Hilt 적용

kotlin
복사편집
@HiltViewModel
class MainViewModel @Inject constructor() : ViewModel() {
    fun doSomething() {
        Log.d("MainViewModel", "작업 수행 중")
    }
}

ViewModel 주입을 위해 @HiltViewModel과 생성자 @Inject가 필요합니다.


6. Fragment에서 주입받기

kotlin
복사편집
@AndroidEntryPoint
class MainFragment : Fragment() {

    private val viewModel: MainViewModel by viewModels()
    @Inject lateinit var cat: Cat

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        cat.speek()
        viewModel.doSomething()
    }
}

@AndroidEntryPoint를 선언한 후 @Inject 혹은 by viewModels()로 의존 객체를 주입받습니다.

Koin vs Hilt

대표적인 DI 라이브러리인 Koin과 Hilt 중에서 어느 라이브러리를 사용해야할까요? 각 라이브러리의 장단점이 다르기에 어떤 라이브러리를 추천하기는 어렵습니다. 진행하려는 프로젝트의 규모와 서비스 방향에 따른 앱 구성에 따라서 적절하게 라이브러리를 선택하면 됩니다

사용성 예시 차이

Koin (Kotlin DSL 사용):

kotlin
복사편집
val appModule = module {
    single { Animal() }
    factory { Cat(get()) }
}

Hilt (Annotation 기반 사용):

kotlin
복사편집
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
    @Provides
    fun provideAnimal(): Animal = Animal()
}

→ Koin은 코틀린 코드로 직관적이고 선언적으로 정의할 수 있는 반면,

→ Hilt는 어노테이션과 코드 생성기를 통해 정확하지만 복잡한 구성을 요구합니다.

 

 

Koin vs Hilt 비교 

항목 Koin Hilt

기반 기술 Kotlin DSL Dagger 기반 (Annotation Processor)
의존성 주입 시점 런타임 (동적 주입) 컴파일 타임 (정적 분석)
성능 상대적으로 낮음 (리플렉션 사용) 높음 (리플렉션 사용 없음)
러닝 커브 낮음 (빠르게 도입 가능) 높음 (개념/구조 이해 필요)
안드로이드 컴포넌트 통합 간접 지원 (ViewModel 등 일부 수동 처리 필요) Android 컴포넌트에 최적화 (Activity, Fragment 등 자동 주입 지원)
모듈화 지원 (멀티모듈) 상대적으로 불리 (의존성 공유 어려움) 강력한 모듈 분리 지원 (공식적으로 멀티모듈 설계 고려됨)
ViewModel 주입 별도 라이브러리 필요 (koin-androidx-viewmodel) 기본 지원 (@HiltViewModel)
테스트 환경 구성 쉬움 (Mock 주입 간단) 상대적으로 복잡 (테스트 전용 모듈 구성 필요)
빌드 속도 빠름 (어노테이션 미사용) 상대적으로 느릴 수 있음 (KAPT 사용)
학습자료 / 문서화 비교적 적음 (비공식 자료 다수) Android 공식 문서 및 예제가 풍부

내부 동작 구조 차이

측면 Koin Hilt

DI 그래프 생성 시점 앱 실행 중 (런타임) 컴파일 시점
문제 발생 시점 실행 중 오류 발생 가능성 존재 컴파일 타임에 대부분의 오류 확인 가능

결론

DI는 유지보수성과 테스트 용이성을 위한 핵심적인 설계 패턴입니다.

  • Koin은 간편하게 DI를 도입하고 싶은 소규모 또는 프로토타입 프로젝트에 적합합니다.
  • Hilt는 공식 권장 프레임워크로, 중대형 프로젝트나 멀티모듈 환경에서의 구조화된 DI 적용에 유리합니다.