[DI] Hilt -> Koin Change

수호·2025년 11월 30일
post-thumbnail

1. Koin이 의존성 주입을 하는 방식

→ Koin = 런타임(Dynamic) DI 컨테이너

→ Hilt/Dagger = 컴파일 타임(Code-Generated) DI 컨테이너

→ 즉, Koin은 코드를 생성하지 않고, 앱 실행 중에 Service Locator 방식 + DSL 모듈 등록으로 DI를 수행함

2. Koin의 기본 원리

  1. Koin 시작

    startKoin{
    	androidContext(this@BeaverPayApp)
      modules(
          appModule,
          presentersModule,
          domainModule,
          dataModule,
          localModule,
          remoteModule,
          payonModule
      )
    }

    → 이렇게 하면 KoinApplication 객체가 생성되며 내부에 Instance Registry가 만들어짐

  1. 정의된 module 등록

    val dataModule = module {
        includes(localModule) // local Module 포함
        single<UserRepository> {
            UserRepositoryImpl(get()) // get() -> LocalUserDataSource
        }
        single<PayonCardReaderRepository> {
            PayonCardReaderRepositoryImpl(get())
        }
    }
    
    val domainModule = module {
        // UseCase
        factory { CheckUserUseCase(get()) }
        factory { InsertUserUseCase(get()) }
        factory { PayonCardReaderUseCase(get()) }
    }
    
    val presentersModule = module {
        viewModel { ReaderViewModel(get()) }
        viewModel { LoginViewModel(get(), get()) }
        viewModel { MainViewModel() }
    }

    → 모듈에 등록되면 Koin은 이를 Key-Value 형태로 저장함

    KeyValue
    UserRepository정의된 Provider 함수
    ReaderViewModelProvider 함수
  2. DI 발생 (get(), Inject(), viewModel() 호출 순간)

    val repo: UserRepository = get()

    → 이 순간 Koin은 다음 절차를 수행

    ex)

    val dataModule = module {
    		single<UserRepository> { UserRepositoryImpl(get()) }
    }
    
    /*
    * 1. single<UserRepository> → "UserRepository 타입이 필요할 때, 이걸 써라" 라는 정의
    * 2. {UserRepositoryImpl(get())} → "생성 시 LocalUserDataSource 같은 의존성을 get() 해서 넣어라"
    * 3. 즉, UserRepository가 필요한 클래스의 생성자에서 요청될 때,
    *    Koin이 알아서 UserRepositoryImpl 인스턴스를 만들어 넣습니다.
    * 4. class ReaderViewModel(private val userRepository: UserResposigory)을 호출할 때 
    *    Koin 내부에서 get()이 자동으로 실행되는 것이다.
    */

    → Koin의 DI 동작 흐름

    get<UserRepository>() 요청
    				****
    Koin Registry에 등록된 UserRepository Provider 찾기
    				****
    싱글톤? → 이미 instance 있으면 반환
    				↓
    없으면 Provider 함수 실행
    				↓
    UserRepositoryImpl(get()) 실행
    				↓
    내부 get() 으로 의존성 탐색 반복
    				↓
    UserRepositoryImpl 인스턴스 생성
    				↓
    Registry에 캐싱(싱글톤이면)
    				↓
    결과 반환

    : 런타임에 필요한 객체를 모듈에서 찾아 직접 생성 → 반환하는 방식

  1. Koin의 DI 특징
    • 런타임 해결 → Koin은 실행 중에 객체 그래프를 계산함
    • 코드 생성 없음 → Hilt/Dagger처럼 코드가 자동 생성되지 않음. → 그래서 빌드가 빠르고 구조가 단순
    • DSL 중심 → 모든 DI 설정은 module {} DSL로 구성
  1. Hilt와 비교하면?

    항목KoinHilt
    DI 방식런타임컴파일 타임
    성능느릴 수 있음(런타임 분석)빠름(코드 생성)
    안정성런타임 오류 ↑컴파일 오류로 조기 발견
    코드량적음많음(Annotation 필요)
    KMP 지원🌟 좋음없음
    멀티 모듈쉬움복잡함
    학습 난이도쉬움어려움
  2. Compose에서 ViewModel 주입

    • Navigaton 내 :
      val viewModel: LoginViewModel = koinViewModel()
    • 뒤에서 Koin은 :
      • 현재 NavBackStackEntry Scope 확인
      • ViewModelFactory 생성
      • 모듈에서 LoginViewModel 정의 찾음
      • 필요 의존성 get() + 인스턴스 생성
      • Lifecycle에 바인딩
  3. 왜 Koin이 KMP에 적합한가?

    • Koin:
      • Kotlin Multiplatform 공식 지원
      • IOS에서도 Kotlin 객체를 주입 가능
      • Annotation Processor(KSP/KAPT) 필요 없음
    • Hilt
      • Android Studio용 → KMP 지원 불가능
      • 코드 생성이 플랫폼 종속이라 IOS 미지원

    → 결국 KMP 프로젝트는 Hilt 대신 Koin 사용이 정답.

요약

→ Koin은 런타임 DI 컨테이너로, 등록된 module에서 객체를 찾아 직접 생성하여 의존성을 해결한다.

코드 생성 없이 DSL 기반이라 구조가 단순하고 KMP 지원이 매우 좋다.

profile
처음부터 다시 시작!!

0개의 댓글