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

Insert Koin

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.
Avatar for Kirill Rozov 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?

Avatar for Kirill Rozov

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