Koin - Activity Scope

강성우, 30 July 2020

Koin에서 Activity scope를 적용한 경험을 정리하였다. 이 내용을 잘 응용하면 custom scope를 적용하는 대 에도 무리가 없을것 으로 생각 된다.

1. Module definition

module()DSL 함수를 이용하여 모듈을 정의 하는데 scope()를 이용해 Custom scope를 적용 한다. scope()함수 에 포함될 함수들의 구현들은 Custom scope의 라이프사이클에 동기화 되어 적용 된다.

scope()함수에는 적용할 Custom scope에 대해 named()함수를 이용하여 이름을 적용해 퀄리파이어 처럼 사용할 수 있다. 이는 스코프를 구분하는데 사용 된다. 아래 예제코드의 경우 named<T>()와 같이 제네릭을 사용하여 클래스 자체를 이름으로 적용 하였다.

val activityModules = module {
    scope(named<PokemonSearchActivity>()) {
        scoped<PokemonNavigationHelper> {
            PokemonNavigationHelperImpl(get<PokemonSearchActivity>())
        }
        viewModel { PokeSearchViewModel(get(), get(), get(), get()) }
        viewModel { PokeDetailViewModel(get(), get(), get()) }
    }
}

위 예제코드의 모듈 정의를 보면 다음과 같다.

  • PokemonSearchActivity의 라이프사이클을 갖는 커스텀 스코프를 적용한 모듈을 정의 한다.
  • PokemonSearchActivity scope에서는 PokemonNavigationHelper인스턴스가 해당 scope에만 사용 가능 하다.
  • PokeSearchViewModel, PokeDetailViewModel 또한 해당 scope에서만 사용 가능 하다.

2. Scope declare

scope를 적용할 Activity에서 scope에 대한 라이프사이클을 적용해야 한다.

class PokemonSearchActivity : AppCompatActivity() {
    // module에서 정의한 Activity scope를 얻는다. 
    private val activityScope =
        getKoin().getOrCreateScope(POKEMON_SEARCH_ACTIVITY_SCOPEID, named<PokemonSearchActivity>())

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.pokesearch_activity)
        // 얻은 scope를 통해서 이 Activity를 사용할수 있음을 선언한다. 
        // 선언된 인스턴스가 이미 존재 할 경우 `overrdie = true`로 설정 하여 재정의 할 수 있도록 해준다. 
        activityScope.declare(this, override = true)
    }

    override fun onDestroy() {
        // Activity가 종료될 시점에 Acitivty의 scope도 같이 종료 시켜준다. 
        activityScope.close()
        super.onDestroy()
    }
}

정리하면, 모듈에서 정의한 custom scope를 특정 인스턴스의 create - destroy 각 시점에 맞추어 선언해주고, 종료시켜주는 것 이다.

custom scope를 생성할때 사용되는 POKEMON_SEARCH_ACTIVITY_SCOPEID는 Scope에 대한 ID로서 예제에서는 문자열로 되어 있다.

3. Examples

예를 들어 위 모듈에서 scope에 적용했던 ViewModel을 주입받는 방법을 보면 아래와 같다.

class PokemonSearchFragment : BaseFragment() {
    private val activityScope = getKoin().getScope(POKEMON_SEARCH_ACTIVITY_SCOPEID)
    private val vm: PokeSearchViewModel by lazy {
        activityScope.getViewModel<PokeSearchViewModel>(requireParentFragment())
    }
    
    // ...
}

Fragment에서는 scope에 대한 인스턴스를 POKEMON_SEARCH_ACTIVITY_SCOPEID라는 scope id(문자열)을 통해 얻고 이를 이용해서 getViewModel()DSL 함수를 이용해 ViewModel인스턴스를 주입받음을 알수있다.