Koin에 대해 알아보자 | Android Study

🤔 Koin?

Koin is a pragmatic and lightweight dependency injection framework for Kotlin developers.
코인은 코틀린 개발자를 위해 개발된 실용적이고 가벼운 의존성 주입 프레임워크입니다. - Koin

안드로이드 진영에서 사용되는 의존성 주입 프레임워크는 대표적으로 Dagger, Hilt, Koin 등이 있는데, Koin은 이중에서도 가장 낮은 러닝 커브를 자랑하여 의존성 주입에 입문하기 굉장히 좋은 프레임워크이다.

의존성 주입?

필요한 객체를 직접 생성하지 않고 외부로부터 객체를 넘겨 받아서 사용하는 것을 의미한다.

// 의존성 주입 X
class Computer {
	Monitor monitor = new Monitor(); // 컴퓨터는 모니터에 의존성을 가지고 있다.
    // Monitor 가 변경되면...?
}

// 의존성 주입
class Computer {
	Monitor monitor;
    
    public Computer(Monitor monitor) { // 의존성 주입
    	this.monitor = monitor;
    }
}

의존성 주입에 관련된 글은 추후에 자세히 작성할 예정인데, 의존성 주입을 함으로서 얻는 이점은 아래와 같다.

  • 객체간의 결합도를 줄이고 코드의 재활용성을 높인다.
  • 코드의 유연성과 확장성이 높아진다.
  • 단위 테스트의 편의성을 높일 수 있다.

🙃 실습

실습을 하면서 키워드를 익혀보도록 하자.

기본 설정

dependencies {
    // Koin
    implementation("io.insert-koin:koin-core:3.1.2")
    implementation("io.insert-koin:koin-android:3.1.2")
}

클래스 생성

class SingleInstance {

    companion object {
        var count : Int = 0
    }

    init { // 객체 생성시마다 1씩 증가하며 생성된 새 객체의 수를 추적하는데 사용한다.
        ++count
    }

    fun hello() = "i am single instance number $count"

}

class FactoryInstance {

    companion object {
        var count : Int = 0
    }

    init { // 객체 생성시마다 1씩 증가하며 생성된 새 객체의 수를 추적하는데 사용한다.
        ++count
    }

    fun hello() = "i am factory instance number $count"

}

class SampleRepository() {
    fun hello() = "Hello Sample Repository"
}

class SampleViewModel(private val repository: SampleRepository) : ViewModel() {
    fun hello() = repository.hello()
}

샘플로 사용할 클래스를 생성한다.

모듈 선언

val singleModule = module {
    single { SingleInstance() }
}

val factoryModule = module {
    factory { FactoryInstance() }
}

val repositoryModule = module {
    single { SampleRepository()}
}

// Repository 모듈이 선언되어 있지 않으면 정상 작동하지 않는다.
val viewModelModule = module {
    viewModel { SampleViewModel(get()) }
}
  • single : 컨테이너 내에서 단 한번만 생성된다.
  • factory : 컨테이너 내에서 요청 시점마다 새로운 객체를 생성한다.
  • get() : 컨테이너 내에서 알맞은 의존성을 주입한다. (위 코드에서는 SampleRepository가 주입된다.)
  • viewModel : 컨테이너 내에 해당 viewModel을 등록한다. (ViewModelFactory 안에 Koin이 알아서 등록해준다.)

컨테이너 생성

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()

		// 컨테이너 등록
		// Application 이 파괴되면 컨테이너도 함께 파괴
        startKoin { 
            androidContext(this@MyApplication)
			modules(singleModule, factoryModule, repositoryModule, viewModelModule) // 모듈 등록
        }
    }

}

의존성 주입을 위한 컨테이너를 생성한다. 이 컨테이너는 Application의 생명주기에 맞게 생성되고 삭제된다. (액티비티 내부에서 컨테이너를 생성했다면 액티비티의 생명주기를 따를 것이다.)

주의점으로, Application 클래스는 생성 후 AndroidManifest.xml에서 새롭게 등록해주어야 한다.

    <application
        android:name=".MyApplication"
        . . .  >
		
    </application>

실제 사용

inject() 키워드를 이용하여 지연 초기화로 의존성을 주입할 수 있다. 뷰모델의 경우 viewModel() 키워드를 사용하여 주입하며, 마찬가지로 지연 초기화의 방법이다. (불변 객체, 즉 val 에만 사용 가능)

만약 지연 초기화 하고 싶지 않다면 각각 get(), getViewModel()을 사용하면 된다. (var 에도 사용 가능)

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)


		// by inject() 를 사용하여 의존성 주입, 사용할 때 초기화된다. (lazy init)
        val singleInstance : SingleInstance by inject()
        val singleInstance2 : SingleInstance by inject()
        val singleInstance3 : SingleInstance by inject()
        val factoryInstance : FactoryInstance by inject()
        val factoryInstance2 : FactoryInstance by inject()
        val factoryInstance3 : FactoryInstance by inject()

        // val repository : SampleRepository by inject()
        val viewModel : SampleViewModel by viewModel()
        
        // 지연 초기화하지 않는 방법
        var nonLazySingleInstance : SingleInstance = get()
        var nonLazyViewModel : SampleViewModel = getViewModel()

        // 등록된 객체의 인스턴스 요청
        // 싱글톤 객체의 경우 카운트가 늘어나지 않는 모습 확인
        log(singleInstance.hello())
        log(singleInstance2.hello())
        log(singleInstance3.hello())

        // 팩토리 객체는 생성할 때마다 카운트가 늘어나는 모습 확인
        // 의존성을 요청할 때마다 새로운 객체 생성
        log(factoryInstance.hello())
        log(factoryInstance2.hello())
        log(factoryInstance3.hello())

        // log(repository.hello())
        log(viewModel.hello())
    }
}

위에서 이야기했듯이 single 모듈은 컨테이너 내에서 단 한번만 생성되는 반면, factory 모듈은 여러번 생성되어 카운트가 증가된 모습을 확인할 수 있다. 또한, get() 키워드를 이용하여 컨테이너 내에서 알맞은 의존성이 주입된 것을 확인할 수 있다.


참고 및 출처

Spoqa logo 기술 블로그

좋은 웹페이지 즐겨찾기