Pro Yearly is on sale from $80 to $50! »

'Koin' 으로 Android DI 하기

'Koin' 으로 Android DI 하기

GDG DevFest Daejeon 2018 에서 발표한 자료

354271902cd8ba2762d05b251dfa0f84?s=128

pluulove (노현석)

November 17, 2018
Tweet

Transcript

  1. Pluu, Yanolja @pluulove 'Koin' Android DI

  2. pluulove Android Developer GDG Korea Android Organizer in Yanolja

  3. Instance Injection DI Dagger, V2 DI , DI KOIN

  4. ! DI ! KOIN !

  5. Single responsibility https://ko.wikipedia.org/wiki/SOLID_( _ _ ) Open/ closed Liskov substitution

    Interface segregation Dependency inversion D I L O S
  6. Layered Architecture Database Persistence Service Business Presentation

  7. Android CleanArchitecture https://cdn-ak.f.st-hatena.com/images/fotolife/s/sys1yagi/20170624/20170624213755.png

  8. None
  9. None
  10. DI

  11. Dependency Injection

  12. Model Database Fetch Dependency

  13. What is KOIN?

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

    in pure Kotlin using functional resolution only: no proxy, no code generation, no reflection!"
  15. Koin 1.0.0 Unleashed (18.09.14)

  16. Support

  17. Setup // Add Jcenter to your repositories if needed repositories

    { jcenter() } dependencies { // Koin for Kotlin apps compile 'org.koin:koin-core:{revnumber}' }
  18. Koin DSL

  19. Koin DSL ! module Koin Module ! factory Instance !

    single Singleton Instance ! get Dependency Component ! bind Bind
  20. 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
  21. val appModule = module { single { Repository() } module("view")

    { factory { View() } factory { Presenter(get()) } } } Define Module Singletone Sub Module Factory Instance Factory Instance - Injection
  22. 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
  23. 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() } }
  24. Binding an interface val myModule = module { single {

    ServiceImpl() } factory { TestA(get()) } factory { TestB(get()) } } class TestA(val service: Service) class TestB(val service: ServiceImpl)
  25. 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
  26. 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)
  27. val myModule = module { single<Service> { ServiceImpl() } single<Service>

    { ServiceImpl() } } val service : Service by inject() Naming a definition
  28. 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.
  29. val myModule = module { single<Service>("default") { ServiceImpl() } single<Service>("test")

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

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

    } Dealing with generics
  32. 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.
  33. module { single(name = "Ints") { ArrayList<Int>() } single(name =

    "Strings") { ArrayList<String>() } } Dealing with generics
  34. startKoin(listOf(module1,module2 ...)) startKoin

  35. fun startKoin( list: List<Module>, useEnvironmentProperties: Boolean = false, useKoinPropertiesFile: Boolean

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

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

    Refactoring Step 01 Module Search
  38. Modules & Namespaces

  39. Koin Module = Koin " "

  40. fun module( path: String = Path.ROOT, createOnStart: Boolean = false,

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

    module { ... } // de nitions in /org/sample namespace val sampleModule = module("org.sample") { ... } Modules with paths
  42. Modules with paths Root Path Definition Sub Path

  43. module { module("org.sample") { // ... } } Modules with

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

    } single { ComponentB(get()) } } module { single { ComponentA() } single { ComponentC(get()) } } } Implicit definitions naming Empty
  45. 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" )
  46. // ComponentB <- ComponentA class ComponentA() class ComponentB(val componentA :

    ComponentA) val moduleA = module { single { ComponentA() } } val moduleB = module { single { ComponentB(get()) } } Linking definitions between modules
  47. // 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
  48. Start the container

  49. /** * 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
  50. fun stopKoin() Stop Koin - closing all resources

  51. Properties

  52. 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
  53. Koin Components

  54. What we need now is an API to retrieve our

    instances outside of the container. That s the goal of Koin components.
  55. !55 Remind Step 03 startKoin & injection Step 02 Refactoring

    Module Search Step 01
  56. Start Koin with myModule fun main(vararg args : String){ //

    Start Koin startKoin(listOf(myModule)) // Create MyComponent instance and inject from Koin container MyComponent() }
  57. 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() }
  58. Unlock the Koin API with KoinComponents ! by inject() -

    Koin ! get() - Koin ! release() - ! getProperty()/ setProperty() - get/set
  59. 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
  60. Injection parameters

  61. 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) } }
  62. 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) } }
  63. None
  64. Setup // Add Jcenter to your repositories if needed repositories

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

    Start Koin startKoin(this, listOf(appModule)) } } Start Koin
  66. 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
  67. ViewModel

  68. 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}' }
  69. Injecting your ViewModel ! by viewModel()
 - Lazy Delegate Property

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

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

    single instance of HelloRepository single<HelloRepository> { HelloRepositoryImpl() } // MyViewModel ViewModel viewModel { MyViewModel(get()) } }
  72. 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
  73. 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
  74. Pros / Cons

  75. Logger , , 
 Reflection Fast Build Pros / Cons

    Service Locator Pattern Overhead Runtime Error Constructor Injection Not Support Pros Cons
  76. Easy Moving from Dagger to Koin   Simplify your Android development

  77. https://github.com/InsertKoinIO/koin/tree/master/koin-projects/examples/coffee-maker Logger (1/2)

  78. https://github.com/InsertKoinIO/koin/tree/master/koin-projects/examples/coffee-maker Logger (2/2)

  79. 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
  80. 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
  81. ! Dependency Injection Pattern ! Factory Pattern Service Locator Pattern

  82. Service Locator Pattern Step 03 Injection Step 02 Generate Search

    Step 01
  83. Reddit https://www.reddit.com/r/androiddev/comments/8ch4cg/dagger2_vs_koin_for_dependency_injection/

  84. It's your turn Setting Search Developer Layer Scalability Testability

  85. ! 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?
  86. Pluu, Yanolja @pluulove ! Kotlin .

  87. Q&A