본문 바로가기
Develop/KMP

[KMP] Hilt → Koin 마이그레이션

by bona.com 2025. 2. 14.

최근 드로이드나이츠 앱을 KMP로 마이그레이션 하는 프로젝트를 진행 중이다. 

이때 KMP에서는 안드로이드 전용 라이브러리를 그대로 사용할 수 없기 때문에, 기존 Android 코드로로 작성된 부분을 KMP에서도 호환 가능한 라이브러리로 교체해야 한다. 

이번 글에서는 그 과정 중 하나로, 의존성 주입 라이브러리를 Hilt에서 Koin으로 마이그레이션 한 과정을 공유해보려고 한다.

 

Koin

우선 Koin 이란, 모든 Kotlin 애플리케이션(ex. 멀티플랫폼, Android, 백엔드)에 종속성 주입을 쉽고 효율적으로 통합하는 방법을 제공하는 DI 라이브러리이다.

 

그 외의 특징들로는 아래와 같다.

  • 읽기 쉽고, 사용하기 쉬우며, 어떤 종류의 애플리케이션이든 작성할 수 있는 Kotlin DSL이다.
  • Android 생태계와는 다른 종류의 통합을 제공하며, Ktor와 같은 더 많은 백엔드 요구 사항에 도달한다.
  • 주석과 함께 사용을 허용한다.

 

Hilt → Koin 마이그레이션

이제 본격적으로 Koin으로 마이그레이션 한 과정을 설명해보겠다.

참고로, 필자는 Koin Annotation을 적용하였다. 그 이유는 어노테이션만으로 의존성 모듈에 객체를 등록할 수 있고, 컴파일 시점에 의존성 검증이 가능하여 안정적이기 때문이다.

더 자세한 이야기는 아래에서 계속 하겠다!

 

✅1. 의존성 관리

아래의 공식문서를 참고해서 의존성을 넣어주었다.

 

Koin Annotations | Koin

Setup Koin Annotations for your project

insert-koin.io

 

Koin Annotation을 사용하기 위해서는 기존 koin 라이브러리에 아래 라이브러리 추가해줘야 한다.

koin-annotations = { module = "io.insert-koin:koin-annotations", version.ref = "koin-annotations" }
koin-ksp-compiler = { module = "io.insert-koin:koin-ksp-compiler", version.ref = "koin-annotations" }

 

그리고 아래처럼 적용해주면 된다. 멀티플랫폼 환경이기에 commonMain 블록 안에 작성을 해 주었다.

kotlin {
    sourceSets {
        androidMain.dependencies { }
        commonMain.dependencies {
            api(libs.koin.core)
            implementation(libs.koin.compose)
            api(libs.koin.annotations)
        }
        iosMain.dependencies { }
    }
}

dependencies {
    add("kspCommonMainMetadata", libs.koin.ksp.compiler)
    add("kspAndroid", libs.koin.ksp.compiler)
    add("kspIosArm64", libs.koin.ksp.compiler)
    add("kspIosSimulatorArm64", libs.koin.ksp.compiler)
}

 

추가로 아래 선언을 통해 컴파일러 플러그인이 컴파일 시 Koin 구성을 확인할 수 있도록 한다.

ksp {
    arg("KOIN_CONFIG_CHECK", "true")
    arg("KOIN_DEFAULT_MODULE", "false")
}

 

2. 플러그인 만들기

드로이드나이츠의 경우 멀티모듈을 사용하고 있기 때문에 DI가 필요한 모듈마다 위의 의존성을 일일이 넣어주는 것은 불필요하다고 생각했다. 

그래서 Koin도 별도의 플러그인 함수로 만들어주고자 했다.

internal fun Project.configureKoinKotlin() {
    with(pluginManager) {
        apply("com.google.devtools.ksp")
    }

    dependencies {
        "implementation"(libs.findLibrary("koin-core").get())
        "implementation"(libs.findLibrary("koin-annotations").get())
        "implementation"(libs.findLibrary("koin-compose").get())
        "implementation"(libs.findLibrary("koin-compose-viewmodel").get())
        "implementation"(libs.findLibrary("koin-compose-viewmodel-navigation").get())
        add("ksp", libs.findLibrary("koin-ksp-compiler").get())
    }
}

 

3. 본격적인 시작

Hilt에서 Koin으로 마이그레이션 하는 것은 "이론상" 쉽다.

어노테이션들만 바꿔주면 되기 때문이다.

 

어떤 Koin 어노테이션으로 바꿔야 하는지 알아야 하기 때문에 그 종류를 알아보자.

  • @Single: 의존성을 싱글톤으로 설정한다.
  • @Factory: 의존성이 주입될 때마다 매번 새로운 인스턴스를 생성한다. 
  • @KoinViewModel: 안드로이드 ViewModel을 주입할 때 사용한다.
  • @Module: 모듈임을 선언한다.
  • @ComponentScan: 지정된 패키지 내에서 Koin 어노테이션을 스캔하여 모듈에 등록한다.

 

이제 이 어노테이션들을 각각 필요한 곳에 잘 배치해주면 된다.

예를 들어, 레포지토리의 경우 객체를 생성해서 주입해주어야 하기 때문에 @Single 어노테이션을 붙여준다.

 

그럼 기존에 이 레포지토리의 의존성을 주입해줬던 모듈에 @ComponentScan을 추가해준다.

 

UseCase의 경우, 필요할 때 일회성으로 생성해야 하기 때문에 @Factory 어노테이션으로 등록해준다.

 

ViewModel은 @HiltViewModel에서 @KoinViewModel로 변경해 준다.

 

아, 물론 기존 Hilt 어노테이션들은 다 지워줘야 한다! 

 

4. Koin 모듈 등록하기

마지막으로 지금까지 생성한 모듈들을 Application의 클래스에 등록해준다.

onCreate()의 startKoin 블록 안에 넣어주면 된다.

class DroidKnightsApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        startKoin {
            androidContext(this@DroidKnightsApplication)
            androidLogger()
            modules(
                ApiModule().module
            )
        }
    }
}

 

트러블 슈팅

아마 위처럼 어노테이션만 바꿔줬다면 오류 투성이었을 것이다...

다음은 내가 겪은 트러블 슈팅들이다.

 

1. abstract 클래스 사용 불가

기존에 @Binds를 사용해 주기 위해 abstract class로 모듈을 작성했었다. 그러나 아래와 같은 에러를 만날 수 있을 것이다.

 

Hilt와 Koin의 동작 방식이 다르기 때문에 발생한 오류이다.

Koin의 @Module 내 함수는 런타임에 의존성을 등록할 때 실제로 호출을 하는데 abstract class일 경우 인스턴스화할 수 없기 때문이다.

 

따라서 abstract class를 class로 바꿔줘야 한다.

// 변경 전
@InstallIn(SingletonComponent::class)
@Module
internal abstract class DataModule { }

// 변경 후
@Module
@ComponentScan
class DataModule { }

 

2. 접근 제어자 변경

Koin은 모듈을 Application 클래스에 등록해줘야 하기 때문에 internal로 작성된 것은 모두 public 접근 제어자로 바꿔줘야 할 것이다.

// 변경 전
internal interface GithubApi { }

// 변경 후
interface GithubApi { }

 

마치며

이렇게 의존성 주입 라이브러리를 Hilt에서 Koin으로 바꾼 과정을 알아보았다!

Koin과 KMP모두 처음이었기 때문에 시간은 오래 걸렸었지만 마치고 나니 너무 뿌듯했던 작업이었다.

 

그래서 Hilt와 Koin 중 어느 것이 더 좋았냐고 물어본다면.. 각자의 장단점이 있기 때문에 상황과 필요에 맞춰 적절히 쓰는 것이 좋을 것 같다고 답변을 할 것이다!

 

다만, 진입장벽은 확실히 Koin이 더 낮다고 느꼈고, Kotlin에 특화된 라이브러리라는 것도 체감할 수 있었다.

한편, 접근제어자를 모두 public으로 바꾸면서 캡슐화를 해치는 단점이 있기 때문에 이러한 특성들을 잘 이해하고 프로젝트 성격에 맞게 신중히 선택하길 권한다!

 

이 모든 과정이 궁금하시다면 아래 PR로 확인부탁드립니다!

https://github.com/Kotlin-Multiplatform-Laboratory/DroidKnightsApp-KMP/pull/9

 

[feature/koin] Migrate from Hilt to Koin by leeeyubin · Pull Request #9 · Kotlin-Multiplatform-Laboratory/DroidKnightsApp-KMP

Overview Hilt 에서 Koin으로 마이그레이션 했습니다!

github.com