Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Insert Koin

Kirill Rozov
November 13, 2018

Insert Koin

Koin is the next step to becoming a project only with Kotlin code. What inside Koin? Only DSL & Kotlin magic. It is possible to effectively replace Dagger 2? Let’s find out does Dependency Injection + Kotlin + Easy of Use == Koin?

Kirill Rozov

November 13, 2018
Tweet

More Decks by Kirill Rozov

Other Decks in Programming

Transcript

  1. Inversion of Control (IoC) is a design principle in which

    custom-written portions of a computer program receive the flow of control from a generic framework wikipedia.org/wiki/Inversion_of_control
  2. Popular DI • Guice • Spring DI • Square Dagger

    • Google Dagger 2 • Java EE CDI • PicoContainer • Kodein • Koin
  3. Sample val sampleModule = module { single { ComponentA() }

    } class Application : KoinComponent { val component by inject<ComponentA>() }
  4. Sample val sampleModule = module { single { ComponentA() }

    } class Application : KoinComponent { val component by inject<ComponentA>() }
  5. Sample val sampleModule = module { single { ComponentA() }

    } class Application : KoinComponent { val component by inject<ComponentA>() }
  6. Sample val sampleModule = module { single { ComponentA() }

    } class Application : KoinComponent { val component by inject<ComponentA>() } fun main(vararg args: String) { startKoin(listOf(sampleModule)) }
  7. Provide dependencies module { single { ComponentB() } single {

    ComponentA(get<ComponentB>()) } } class ComponentA(c : ComponentB)
  8. Properties module { factory { RestService(getProperty(“url”)) } } class RestService(url:

    String) module { factory { RestService(getProperty(“url”, “http://localhost”)) } }
  9. Properties module { factory { RestService(getProperty(“url”)) } } class RestService(url:

    String) module { factory { RestService(getProperty(“url”, “http://localhost”)) } } val key1Property: String by property(“key1”)
  10. Properties Sources • On initialization • Using KoinComponent.setProperty() • koin.properties

    in assets • koin.properties in JAR resources • Environment variables
  11. Parameters module { factory { (id : String) -> NewsDetailsPresenter(id)

    } } val presenter: NewsDetailPresenter by inject { parametersOf(“sampleNewsId") }
  12. Parameters module { factory { (id : String) -> NewsDetailsPresenter(id)

    } } val presenter: NewsDetailPresenter by inject { parametersOf(“sampleNewsId") }
  13. Named dependencies module { single(name = “debug”) { ComponentB(…) }

    single(name = “prod”) { ComponentB(…) } }
  14. Named dependencies class Application : KoinComponent { val componentProd by

    inject<ComponentB>(“prod”) val componentDebug by inject<ComponentB>(“debug”) } module { single(name = “debug”) { ComponentB(…) } single(name = “prod”) { ComponentB(…) } }
  15. Namespaces val rootModule = module { … } val sampleModule

    = module(path = “org.sample”) { … }
  16. Namespaces module(path = “org.sample”) { … } // is equals

    to module { module(“org”) { module(“sample”) { } } }
  17. Namespaces module { module("B") { factory { ComponentA() } factory

    { ComponentB(get()) } } module(“C") { factory { ComponentA() } factory { ComponentB(get()) } } } class ComponentB(c : ComponentA) class ComponentC(c : ComponentA)
  18. Namespaces module { module("B") { factory { ComponentA() } factory

    { ComponentB(get()) } } module(“C") { factory { ComponentA() } factory { ComponentB(get()) } } } class ComponentB(c : ComponentA) class ComponentC(c : ComponentA)
  19. Namespaces module { module("B") { factory { ComponentA() } factory

    { ComponentB(get()) } } module(“C") { factory { ComponentA() } factory { ComponentB(get()) } } } class ComponentB(c : ComponentA) class ComponentC(c : ComponentA)
  20. Namespaces module { module("B") { factory { ComponentA() } factory

    { ComponentB(get()) } } module(“C") { factory { ComponentA() } factory { ComponentB(get()) } } } class ComponentB(c : ComponentA) class ComponentC(c : ComponentA) // B.ComponentA // B.ComponentB // C.ComponentA // C.ComponentC
  21. Scopes module { scope(scopeId = “scope_id”) { … } }

    // create a scope val session = getKoin().createScope("scope_id") // or get scope if already created before val session = getKoin().getScope("scope_id") // will return the same instance of Presenter until Scope is closed val presenter = get<Presenter>() // close the scope session.close() // instance of presenter has been dropped
  22. Scopes module { scope(scopeId = “scope_id”) { … } }

    // create a scope val session = getKoin().createScope("scope_id") // or get scope if already created before val session = getKoin().getScope("scope_id") // will return the same instance of Presenter until Scope is closed val presenter = get<Presenter>() // close the scope session.close() // instance of presenter has been dropped
  23. Scopes module { scope(scopeId = “scope_id”) { … } }

    // create a scope val session = getKoin().createScope("scope_id") // or get scope if already created before val session = getKoin().getScope("scope_id") // will return the same instance of Presenter until Scope is closed val presenter = get<Presenter>() // close the scope session.close() // instance of presenter has been dropped
  24. Scopes module { scope(scopeId = “scope_id”) { … } }

    // create a scope val session = getKoin().createScope("scope_id") // or get scope if already created before val session = getKoin().getScope("scope_id") // will return the same instance of Presenter until Scope is closed val presenter = get<Presenter>() // close the scope session.close() // instance of presenter has been dropped
  25. Scopes module { scope(scopeId = “scope_id”) { … } }

    // create a scope val session = getKoin().createScope("scope_id") // or get scope if already created before val session = getKoin().getScope("scope_id") // will return the same instance of Presenter until Scope is closed val presenter = get<Presenter>() // close the scope session.close() // instance of presenter has been dropped
  26. Binding scope to lifecycle class MyActivity : AppCompatActivity() { //

    inject Presenter instance, tied to current “session" scope val presenter : Presenter by inject() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // bind current lifecycle to Activity's scope bindScope(getScope("session")) }
  27. Binding scope to lifecycle class MyActivity : AppCompatActivity() { //

    inject Presenter instance, tied to current “session" scope val presenter : Presenter by inject() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // bind current lifecycle to Activity's scope bindScope(getScope("session")) }
  28. module { factory { Presenter(get()) } single { Repository(get()) }

    single<DateSource> { DebugDataSource() } } class Presenter(repository: Repository) class Repository(dateSource: DateSource) interface DateSource class DebugDataSource() : DateSource
  29. module { factory { Presenter(get()) } single { Repository(get()) }

    single<DateSource> { DebugDataSource() } } get<Presenter>() get<Presenter>() class Presenter(repository: Repository) class Repository(dateSource: DateSource) interface DateSource class DebugDataSource() : DateSource
  30. module { factory { Presenter(get()) } single { Repository(get()) }

    single<DateSource> { DebugDataSource() } } get<Presenter>() get<Presenter>() (KOIN)::Resolve [Presenter] ~ Factory[class=Presenter] (KOIN):: Resolve [Repository] ~ Single[class=Repository] (KOIN):: Resolve [Datasource] ~ Single[class=DebugDatasource, binds~(Datasource)] (KOIN):: (*) Created (KOIN):: (*) Created (KOIN)::(*) Created (KOIN)::Resolve [Repository] ~ Factory[class=Repository] (KOIN):: Resolve [DebugDatasource] ~ Single[class=DebugDatasource, binds~(Datasource)] (KOIN)::(*) Created class Presenter(repository: Repository) class Repository(dateSource: DateSource) interface DateSource class DebugDataSource() : DateSource
  31. Missing dependency module { single { ComponentA(get()) } single {

    ComponentB(get()) } } class ComponentA(component: ComponentB) class ComponentB(component: ComponentA) get<ComponentC>()
  32. Missing dependency module { single { ComponentA(get()) } single {

    ComponentB(get()) } } class ComponentA(component: ComponentB) class ComponentB(component: ComponentA) org.koin.error.DependencyResolutionException: No definition found for ComponentC - Check your definitions and contexts visibility get<ComponentC>()
  33. Cyclic dependencies module { single { ComponentA(get()) } single {

    ComponentB(get()) } } class ComponentA(component: ComponentB) class ComponentB(component: ComponentA)
  34. Cyclic dependencies get<ComponentA>() module { single { ComponentA(get()) } single

    { ComponentB(get()) } } class ComponentA(component: ComponentB) class ComponentB(component: ComponentA)
  35. Cyclic dependencies org.koin.error.BeanInstanceCreationException: Can't create bean Bean[class=ComponentA] due to error

    : BeanInstanceCreationException: Can't create bean Bean[class=ComponentB] due to error : BeanInstanceCreationException: Can't create bean Bean[class=ComponentA] due to error : DependencyResolutionException: Cyclic dependency detected while resolving class ComponentA module { single { ComponentA(get()) } single { ComponentB(get()) } } class ComponentA(component: ComponentB) class ComponentB(component: ComponentA) get<ComponentA>()
  36. Tests class SampleTest : KoinTest { val presenter : MyPresenter

    by inject() val repository : Repository by inject() @Before fun before(){ startKoin(listOf(myModule)) } @Test fun testSayHello() { val hello = repository.giveHello() assertEquals(hello, presenter.sayHello()) } @After fun after(){ closeKoin() } }
  37. Tests class SampleTest : KoinTest { val presenter : MyPresenter

    by inject() val repository : Repository by inject() @Before fun before(){ startKoin(listOf(myModule)) } @Test fun testSayHello() { val hello = repository.giveHello() assertEquals(hello, presenter.sayHello()) } @After fun after(){ closeKoin() } }
  38. Tests class SampleTest : KoinTest { val presenter : MyPresenter

    by inject() val repository : Repository by inject() @Before fun before(){ startKoin(listOf(myModule)) } @Test fun testSayHello() { val hello = repository.giveHello() assertEquals(hello, presenter.sayHello()) } @After fun after(){ closeKoin() } }
  39. Tests class SampleTest : KoinTest { val presenter : MyPresenter

    by inject() val repository : Repository by inject() @Before fun before(){ startKoin(listOf(myModule)) } @Test fun testSayHello() { val hello = repository.giveHello() assertEquals(hello, presenter.sayHello()) } @After fun after(){ closeKoin() } }
  40. Tests class SampleTest : KoinTest { val presenter : MyPresenter

    by inject() val repository : Repository by inject() @Before fun before(){ startKoin(listOf(myModule)) } @Test fun testSayHello() { val hello = repository.giveHello() assertEquals(hello, presenter.sayHello()) } @After fun after(){ closeKoin() } }
  41. Declare on the fly class SampleTest : KoinTest { @Test

    fun testSayHello() { startKoin(listOf(myModule)) declare { single<Repository> { RepositoryImpl() } } val presenter : MyPresenter = get() assertEquals(hello, presenter.sayHello()) } }
  42. Mock out of the box class SampleTest : KoinTest {

    @Test fun testSayHello() { startKoin(listOf(myModule)) declareMock<Repository>() val presenter : MyPresenter = get() assertEquals(hello, presenter.sayHello()) } }
  43. Graph validation class ComponentA(component: ComponentB) class ComponentB(component: ComponentC) class ComponentC()

    val sampleModule = module { single { ComponentA(get()) } factory { ComponentB(get()) } }
  44. Graph validation class ComponentA(component: ComponentB) class ComponentB(component: ComponentC) class ComponentC()

    val sampleModule = module { single { ComponentA(get()) } factory { ComponentB(get()) } } class DryRunTest : KoinTest { @Test fun dryRunTest() { startKoin(listOf(sampleModule)) dryRun() } }
  45. Default Way class ListFragment : Fragment() { val listViewModel =

    ViewModelProviders.of(this).get(ListViewModel::class.java) }
  46. Simpler with Kotlin class ListFragment : Fragment() { val listViewModel

    = getViewModel<ListViewModel>() } inline fun <reified VM : ViewModel> Fragment.getViewModel():VM { return viewModelProvider.get(VM::class.java) }
  47. ViewModel with arguments class DetailViewModel(id: String) : ViewModel() class DetailFactory(val

    id: String) : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { if (modelClass == DetailViewModel::class.java) { return DetailViewModel(id) as T } error("Can't create ViewModel for class='$modelClass'") } }
  48. ViewModel with arguments class DetailViewModel(id: String) : ViewModel() class DetailFactory(val

    id: String) : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { if (modelClass == DetailViewModel::class.java) { return DetailViewModel(id) as T } error("Can't create ViewModel for class='$modelClass'") } } class DetailFragment : Fragment() { val listViewModel = getViewModel<DetailViewModel>(DetailFactory(id)) }
  49. Koin Way model { viewModel { (id : String) ->

    DetailViewModel(id) } } class DetailFragment : Fragment() { val detailViewModel by viewModel<DetailViewModel> { parametersOf(“sampleId") } }
  50. What missed from Dagger 2? • Compile time validation •

    Features • Multibindings • Async Injection • Reusable scope
  51. Why use Koin? • Dagger 2 is too complex for

    start with DI • No need Dagger 2 advanced features
  52. Why use Koin? • Dagger 2 is too complex for

    start with DI • No need Dagger 2 advanced features • Single place of dependencies declaration
  53. Why use Koin? • Dagger 2 is too complex for

    start with DI • No need Dagger 2 advanced features • Single place of dependencies declaration • DSL is amazing for dependencies declaration
  54. Why use Koin? • Dagger 2 is too complex for

    start with DI • No need Dagger 2 advanced features • Single place of dependencies declaration • DSL is amazing for dependencies declaration • Support Kotlin Multiplatform projects
  55. Why use Koin? • Dagger 2 is too complex for

    start with DI • No need Dagger 2 advanced features • Single place of dependencies declaration • DSL is amazing for dependencies declaration • Support Kotlin Multiplatform projects • Faster build time
  56. Why use Koin? • Dagger 2 is too complex for

    start with DI • No need Dagger 2 advanced features • Single place of dependencies declaration • DSL is amazing for dependencies declaration • Support Kotlin Multiplatform projects • Faster build time • Pure Kotlin code