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

'Koin' 으로 Android DI 하기

'Koin' 으로 Android DI 하기

GDG DevFest Daejeon 2018 에서 발표한 자료

pluulove (노현석)

November 17, 2018
Tweet

More Decks by pluulove (노현석)

Other Decks in Technology

Transcript

  1. DI

  2. "A pragmatic lightweight dependency injection framework for Kotlin developers. Written

    in pure Kotlin using functional resolution only: no proxy, no code generation, no reflection!"
  3. Setup // Add Jcenter to your repositories if needed repositories

    { jcenter() } dependencies { // Koin for Kotlin apps compile 'org.koin:koin-core:{revnumber}' }
  4. Koin DSL ! module Koin Module ! factory Instance !

    single Singleton Instance ! get Dependency Component ! bind Bind
  5. class Repository() class View() class Presenter( val repository : Repository

    ) Define val appModule = module { single { Repository() } module("view") { factory { View() } factory { Presenter(get()) } } } https://gist.github.com/arnaudgiuliani/d343df6e08a997b2ff29f7542ca1f601#file-koin_1-0_dsl-kt
  6. val appModule = module { single { Repository() } module("view")

    { factory { View() } factory { Presenter(get()) } } } Define Module Singletone Sub Module Factory Instance Factory Instance - Injection
  7. Define - Log [context] create [module] declare Single [name='Repository',class='Repository'] [module]

    declare Factory [name='view.View',class='View', path:'view'] [module] declare Factory [name='view.Presenter',class='Presenter', path:'view'] [modules] loaded 3 de nitions
  8. interface Service { fun doSomething() } class ServiceImpl : Service

    { override fun doSomething() { ... } } Binding an interface https://insert-koin.io/docs/1.0/documentation/koin-core/index.html#_binding_an_interface val myModule = module { single { ServiceImp() } single { ServiceImpl() as Service } single<Service> { ServiceImpl() } }
  9. Binding an interface val myModule = module { single {

    ServiceImpl() } factory { TestA(get()) } factory { TestB(get()) } } class TestA(val service: Service) class TestB(val service: ServiceImpl)
  10. Binding an interface val myModule = module { single {

    ServiceImpl() } factory { TestA(get()) } factory { TestB(get()) } } class TestA(val service: Service) class TestB(val service: ServiceImpl) (KOIN)::[e] Error while resolving instance for class 'Service' - error: org.koin.error.NoBeanDefFoundException: No compatible de nition found for type 'Service'. Check your module de nition (KOIN)::[e] Error while resolving instance for class 'TestA' - error: org.koin.error.BeanInstanceCreationException: Can't create de nition for 'Factory [name='TestA',class='TestA']' due to error : No compatible de nition found for type 'Service'. Check your module de nition
  11. val myModule = module { single { ServiceImpl() } factory

    { TestA(get()) } factory { TestB(get()) } } val myModule = module { single { ServiceImp() } bind Service::class factory { TestA(get()) } factory { TestB(get()) } } Binding an interface class TestA(val service: Service) class TestB(val service: ServiceImpl)
  12. val myModule = module { single<Service> { ServiceImpl() } single<Service>

    { ServiceImpl() } } val service : Service by inject() Naming a definition
  13. val myModule = module { single<Service> { ServiceImpl() } single<Service>

    { ServiceImpl() } } val service : Service by inject() Naming a definition Exception in thread "main" org.koin.error.BeanOverrideException: Try to override de nition with Single [name='ServiceImpl',class='ServiceImpl'], but override is not allowed. Use 'override' option in your de nition or module.
  14. val myModule = module { single<Service>("default") { ServiceImpl() } single<Service>("test")

    { ServiceImpl() } } val service : Service by inject(name = "default") Naming a definition
  15. class Presenter(val view : View) val myModule = module {

    single{ (view : View) -> Presenter(view) } } val presenter : Presenter by inject { parametersOf(view) } Declaring injection parameters
  16. module { single { ArrayList<Int>() } single { ArrayList<String>() }

    } Dealing with generics Exception in thread "main" org.koin.error.BeanOverrideException: Try to override de nition with Single [name='ArrayList',class='java.util.ArrayList'], but override is not allowed. Use 'override' option in your de nition or module.
  17. module { single(name = "Ints") { ArrayList<Int>() } single(name =

    "Strings") { ArrayList<String>() } } Dealing with generics
  18. fun startKoin( list: List<Module>, useEnvironmentProperties: Boolean = false, useKoinPropertiesFile: Boolean

    = false, extraProperties: Map<String, Any> = HashMap(), logger: Logger = PrintLogger() ) startKoin
  19. fun startKoin( list: List<Module>, useEnvironmentProperties: Boolean = false, useKoinPropertiesFile: Boolean

    = false, extraProperties: Map<String, Any> = HashMap(), logger: Logger = PrintLogger() ) startKoin Modules Koin Logger
  20. Step By Step Step 03 startKoin & injection Step 02

    Refactoring Step 01 Module Search
  21. fun module( path: String = Path.ROOT, createOnStart: Boolean = false,

    override: Boolean = false, de nition: ModuleDe nition.() -> Unit ) Module s Path Path
  22. // de nitions in / (root) namespace val aRootModule =

    module { ... } // de nitions in /org/sample namespace val sampleModule = module("org.sample") { ... } Modules with paths
  23. module { module("org.sample") { // ... } } Modules with

    paths module("org.sample") { // } module { module("org") { module("sample") { // ... } } } ALL EQUALS
  24. val aModule = module { module { single { ComponentA()

    } single { ComponentB(get()) } } module { single { ComponentA() } single { ComponentC(get()) } } } Implicit definitions naming Empty
  25. Implicit definitions naming val aModule = module { module("B") {

    single { ComponentA() } single { ComponentB(get()) } } module("C") { single { ComponentA() } single { ComponentC(get()) } } } Directly get<ComponentA>( name = "B.ComponentA" )
  26. // ComponentB <- ComponentA class ComponentA() class ComponentB(val componentA :

    ComponentA) val moduleA = module { single { ComponentA() } } val moduleB = module { single { ComponentB(get()) } } Linking definitions between modules
  27. // de nitions in / val rootModule = module {

    single { ComponentA() } } // de nitions in /org val orgModule = module("org") { single { ComponentB(...) } } // de nitions in /org/sample val sampleModule = module("org.sample") { single { ComponentC(...) } } // de nitions in /org/demo val demoModule = module("org.demo") { single { ComponentD(...) } } Visibility rules CC /sample CA /org CB CD /demo
  28. /** * Load Koin modules - whether Koin is already

    started or not * allow late module de nition load (e.g: libraries ...) * * @param modules : List of Module */ fun loadKoinModules(modules: List<Module>): Koin Loading modules without startKoin function
  29. class MyComponent : KoinComponent { fun doSomething(){ // Read a

    Koin property val serviceUrl = getProperty("server_url") // Set a Koin property setProperty("isDebug",false) } } Read/Write property from a KoinComponent Read Write
  30. What we need now is an API to retrieve our

    instances outside of the container. That s the goal of Koin components.
  31. Start Koin with myModule fun main(vararg args : String){ //

    Start Koin startKoin(listOf(myModule)) // Create MyComponent instance and inject from Koin container MyComponent() }
  32. Use get() & by inject() to inject MyService instance class

    MyComponent : KoinComponent { // lazy inject Koin instance val myService : MyService by inject() // or // eager inject Koin instance val myService : MyService get() }
  33. Unlock the Koin API with KoinComponents ! by inject() -

    Koin ! get() - Koin ! release() - ! getProperty()/ setProperty() - get/set
  34. val module = module { module("ComponentB") { single { ComponentA()

    } single { ComponentB(get()) } } module("ComponentC") { single { ComponentA() } single { ComponentC(get()) } } } val a_b = get<ComponentA>( name = "ComponentB.ComponentA" ) val a_c = get<ComponentA>( name = "ComponentC.ComponentA" ) class ComponentA class ComponentB(val componentA: ComponentA) class ComponentC(val componentA: ComponentA) Resolving instance from a name or a module
  35. Injection parameters Defining an injection parameter class Presenter(val view :

    View) val myModule = module { single { (view : View) -> Presenter(view) } } class MyComponent : View, KoinComponent { // inject this as View value val presenter : Presenter by inject { parametersOf(this) } }
  36. Injection parameters Multiple parameters class Presenter(val view : View, id

    : String) val myModule = module { single { (view : View, id : String) -> Presenter(view,id) } } class MyComponent : View, KoinComponent { val id : String ... // inject with view & id val presenter : Presenter by inject { parametersOf(this,id) } }
  37. Setup // Add Jcenter to your repositories if needed repositories

    { jcenter() } dependencies { // Koin for Android compile 'org.koin:koin-android:{revnumber}' }
  38. class MyApplication : Application(){ override fun onCreate() { super.onCreate() //

    Start Koin startKoin(this, listOf(appModule)) } } Start Koin
  39. class MySimpleActivity : AppCompatActivity() { // Lazy injected MySimplePresenter val

    rstPresenter: MySimplePresenter by inject() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) //... } } Injecting dependencies inject() / get() Activity, Fragment, Service Component Koin
  40. Setup // Add Jcenter to your repositories if needed repositories

    { jcenter() } dependencies { // Koin for Android - Scope feature // include koin-android-scope & koin-android compile 'org.koin:koin-android-viewmodel:{revnumber}' }
  41. Injecting your ViewModel ! by viewModel()
 - Lazy Delegate Property

    ViewModel Injection ! getViewModel()
 - ViewModel ! by sharedViewModel()
 - Lazy Delegate Property ViewModel Injection ! getSharedViewModel()
 - ViewModel
  42. Create a ViewModel class class MyViewModel(val repo : HelloRepository) :

    ViewModel() { fun sayHello() = "${repo.giveHello()} from $this" }
  43. Writing the Koin module val appModule = module { //

    single instance of HelloRepository single<HelloRepository> { HelloRepositoryImpl() } // MyViewModel ViewModel viewModel { MyViewModel(get()) } }
  44. class MyViewModelActivity : AppCompatActivity() { // Lazy Inject ViewModel val

    myViewModel: MyViewModel by viewModel() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_simple) //... } } Injecting dependencies
  45. class WeatherActivity : AppCompatActivity() { /** Declare WeatherViewModel with Koin

    and allow constructor dependency injection */ private val viewModel by viewModel<WeatherViewModel>() } class WeatherFragment : Fragment() { /** Declare shared WeatherViewModel with WeatherActivity */ private val viewModel by sharedViewModel<WeatherViewModel>() } Injecting dependencies
  46. Logger , , 
 Reflection Fast Build Pros / Cons

    Service Locator Pattern Overhead Runtime Error Constructor Injection Not Support Pros Cons
  47. Fast https://medium.com/@charbgr/bye-bye-dagger-1494118dcd41 Module / Compile time Dagger Koin :app 41.610s

    28.374s :module1 26.363s 19.745s module2 14.345s 11.325s module3 7.241s 3.950s module4 22.828s 5.866s module5 4.976s 2.505s module6 5.631s 4.684 module7 1.988s 3.178s SUM 125.15s 79.59s
  48. Overhead https://github.com/InsertKoinIO/koin/issues/281 Injection Time in Kotlin Dagger 2.16 Koin 1.0.1

    Kodein 5.3.0 Min-Max 0.03-9.48 ms 47.41-7.25 ms 7.48-15.48 ms Average 0.12 ms 60.16 ms 7.63 ms KOIN 1.1.0
  49. ! From Dagger to Koin, a step by step migration

    guide ! Inversion of Control Containers and the Dependency Injection pattern ! Moving from Dagger to Koin   Simplify your Android development ! What is dependency injection?
  50. Q&A