Slide 1

Slide 1 text

Dependency injection from zero to hero with Koin Arnaud Giuliani @arnogiu Kotliners 2019

Slide 2

Slide 2 text

The CoffeeMaker App ☕ * aka the thermosiphon dagger’s nightmare sample app

Slide 3

Slide 3 text

Pump +pump() Thermosiphon Heater +off() +on() +isHot() ElectricHeater CoffeeMaker +brew()

Slide 4

Slide 4 text

interface Heater { fun on() fun off() fun isHot() : Boolean }

Slide 5

Slide 5 text

class ElectricHeater : Heater { var heating: Boolean = false override fun on() { println("~ ~ ~ heating ~ ~ ~") heating = true } override fun off() { heating = false } override fun isHot(): Boolean = heating }

Slide 6

Slide 6 text

interface Pump { fun pump() }

Slide 7

Slide 7 text

class Thermosiphon(val heater: Heater) : Pump{ override fun pump() { if (heater.isHot()){ println("=> => pumping => =>") } } }

Slide 8

Slide 8 text

class CoffeeMaker(val pump: Pump, val heater: Heater) { fun brew() { heater.on() pump.pump() println(" [_]P coffee! [_]P ") heater.off() } }

Slide 9

Slide 9 text

class CoffeeApp { val coffeeMaker: CoffeeMaker fun run() { coffeeMaker.brew() } } fun main(vararg args: String) { startKoin(listOf(coffeeMakerModule)) CoffeeApp().run() }

Slide 10

Slide 10 text

Pump +pump() Thermosiphon Heater +off() +on() +isHot() ElectricHeater CoffeeMaker +brew() how to assemble it?

Slide 11

Slide 11 text

Koin

Slide 12

Slide 12 text

- Intuitive DSL to describe it - Lightweight container to run it - Simple API to use it

Slide 13

Slide 13 text

repositories { jcenter() } dependencies { // Koin for Kotlin implementation ‘org.koin:koin-core:$version’ } Gradle Setup

Slide 14

Slide 14 text

class ElectricHeater : Heater class Thermosiphon(val heater: Heater) : Pump class CoffeeMaker(val pump: Pump, val heater: Heater) val coffeeMakerModule = module { single { CoffeeMaker(get(),get()) } single{ Thermosiphon(get()) } single { ElectricHeater() } } * also multi modules

Slide 15

Slide 15 text

class ElectricHeater : Heater class Thermosiphon(val heater: Heater) : Pump class CoffeeMaker(val pump: Pump, val heater: Heater) val coffeeMaker = module { single { CoffeeMaker(get(),get()) } } val coffeeStuffs = module { single{ Thermosiphon(get()) } single { ElectricHeater() } } * also multi modules

Slide 16

Slide 16 text

Koin Component a class that can use the Koin container

Slide 17

Slide 17 text

✅ open access to Kotlin extensions: inject(), get() identify a class linked to the Koin API KoinComponent is an interface marker bootstrap & help integrate with runtime (Android…)

Slide 18

Slide 18 text

class CoffeeApp : KoinComponent { val coffeeMaker: CoffeeMaker by inject() fun run() { coffeeMaker.brew() } } fun main(vararg args: String) { startKoin(listOf(coffeeMakerModule)) CoffeeApp().run() }

Slide 19

Slide 19 text

startKoin to start the Koin container

Slide 20

Slide 20 text

fun run() { coffeeMaker.brew() } } fun main(vararg args: String) { startKoin { modules(coffeeMakerModule) } CoffeeApp().run() }

Slide 21

Slide 21 text

~ ~ ~ heating ~ ~ ~ => => pumping => => [_]P coffee! [_]P

Slide 22

Slide 22 text

Pump +pump() Thermosiphon Heater +off() +on() +isHot() ElectricHeater CoffeeMaker +brew() lazy!

Slide 23

Slide 23 text

val coffeeMakerModule = module { single { CoffeeMaker(get(),lazy { get{ Thermosiphon(get()) } single { ElectricHeater() } }

Slide 24

Slide 24 text

class CoffeeMaker(private val pump: Pump, _heater: Lazy) { val heater by _heater fun brew() { heater.on() pump.pump() println(" [_]P coffee! [_]P ") heater.off() } } * Lazy is a Kotlin type

Slide 25

Slide 25 text

Let’s get back with Dagger ...

Slide 26

Slide 26 text

@Module(includes = [(PumpModule::class)]) internal class DripCoffeeModule { @Provides @Singleton fun provideHeater(): Heater { return ElectricHeater() } }

Slide 27

Slide 27 text

@Module abstract class PumpModule { @Binds abstract fun providePump(pump: Thermosiphon): Pump }

Slide 28

Slide 28 text

@Singleton @Component(modules = [DripCoffeeModule::class]) interface CoffeeApp { fun maker(): CoffeeMaker }

Slide 29

Slide 29 text

class Thermosiphon @Inject constructor(private val heater: Heater) : Pump{ override fun pump() { if (heater.isHot()){ println("=> => pumping => =>") } } }

Slide 30

Slide 30 text

class CoffeeMaker @Inject constructor(private val pump: Pump, private val _heater: Lazy) { val heater by lazy { _heater.get() } fun brew() { heater.on() pump.pump() println(" [_]P coffee! [_]P ") heater.off() } } * Lazy is a Dagger type

Slide 31

Slide 31 text

Just migrate to Koin, what else?

Slide 32

Slide 32 text

Pump +pump() Thermosiphon Heater +off() +on() +isHot() ElectricHeater CoffeeMaker +brew()

Slide 33

Slide 33 text

val coffeeMakerModule = module { single { ElectricHeater() } }

Slide 34

Slide 34 text

class Thermosiphon @Inject constructor(private val heater: Heater) : Pump{ override fun pump() { if (heater.isHot()){ println("=> => pumping => =>") } } }

Slide 35

Slide 35 text

class Thermosiphon : Pump, KoinComponent { private val heater: Heater by inject() override fun pump() { if (heater.isHot()){ println("=> => pumping => =>") } } }

Slide 36

Slide 36 text

class CoffeeMaker @Inject constructor(private val pump: Pump, private val _heater: Lazy) { val heater: Heater by lazy { _heater.get() } fun brew() { heater.on() pump.pump() println(" [_]P coffee! [_]P ") heater.off() } }

Slide 37

Slide 37 text

class CoffeeMaker @Inject constructor(private val pump: Pump) : KoinComponent val heater: Heater by inject() fun brew() { heater.on() pump.pump() println(" [_]P coffee! [_]P ") heater.off() } }

Slide 38

Slide 38 text

http://bit.ly/dagger2koin From Dagger to Koin, a step by step migration guide

Slide 39

Slide 39 text

No content

Slide 40

Slide 40 text

Can’t create Android components directly

Slide 41

Slide 41 text

Activity, Fragment, Service … are extended as KoinComponent! out of the box! ✅ Koin extensions for Android

Slide 42

Slide 42 text

repositories { jcenter() } dependencies { // Koin for Android implementation ‘org.koin:koin-android:$version’ } Gradle Setup

Slide 43

Slide 43 text

class MyApplication : Application() { override fun onCreate() { super.onCreate() // Reference Android context in Koin startKoin { androidLogger() androidContext(this@MyApplication) modules(coffeeMakerModule) } } }

Slide 44

Slide 44 text

class MyActivity : AppCompatActivity() { val coffeeMaker: CoffeeMaker by inject() }

Slide 45

Slide 45 text

Get the Android context?

Slide 46

Slide 46 text

class MyComponent(val context : Context) val myModule = module { single { MyComponent(androidContext()) } }

Slide 47

Slide 47 text

class MyComponent(val context : Context) val myModule = module { single { MyComponent(get()) } }

Slide 48

Slide 48 text

Dealing with transient components? ie. presenters …

Slide 49

Slide 49 text

class MyPresenter val myModule = module { // will create MyPresenter on each call factory { MyPresenter() } } class MyActivity : AppCompatActivity() { }

Slide 50

Slide 50 text

class MyPresenter val myModule = module { // will create MyPresenter on each call factory { MyPresenter() } } class MyActivity : AppCompatActivity() { // new instance on each call val presenter: MyPresenter by inject() }

Slide 51

Slide 51 text

Ready for Android Architecture Components

Slide 52

Slide 52 text

repositories { jcenter() } dependencies { // Koin for Android + ViewModel features implementation ‘org.koin:koin-android-viewmodel:$version’ } Gradle Setup

Slide 53

Slide 53 text

✅ Koin extensions for Android ViewModel

Slide 54

Slide 54 text

class MyViewModel(val coffeeMaker : CoffeeMaker) : ViewModel() val coffeeMakerModule = module { viewModel { MyViewModel(get()) } } class MyActivity : AppCompatActivity() { // inject ViewModel val myViewModel: MyViewModel by viewModel() override fun onCreate(savedInstanceState: Bundle?) { // get ViewModel val myViewModel: MyViewModel = getViewModel() }

Slide 55

Slide 55 text

//... viewModel { MyViewModel(get()) } } class MyActivity : AppCompatActivity() { // inject ViewModel val myViewModel: MyViewModel by viewModel() override fun onCreate(savedInstanceState: Bundle?) { // get ViewModel val myViewModel: MyViewModel = getViewModel() }

Slide 56

Slide 56 text

//... viewModel { MyViewModel(get()) } } class MyFragment : Fragment() { // inject ViewModel val myViewModel: MyViewModel by viewModel() override fun onCreate(savedInstanceState: Bundle?) { // get ViewModel val myViewModel: MyViewModel = getViewModel() }

Slide 57

Slide 57 text

//... viewModel { MyViewModel(get()) } } class MyFragment : Fragment() { // inject ViewModel from parent activity val myViewModel: MyViewModel by sharedViewModel() overrid e fun onCreate(savedInstanceState: Bundle?) { // get ViewModel val myViewModel: MyViewModel = getViewModel() }

Slide 58

Slide 58 text

//... viewModel { MyViewModel(get()) } } class MyFragment : Fragment() { // inject ViewModel from parent activity val myViewModel: MyViewModel by sharedViewModel() overrid e fun onCreate(savedInstanceState: Bundle?) { // get ViewModel val myViewModel: MyViewModel = getViewModel() } val viewModel = ViewModelProviders .of(this.activity) .get(MyViewModel::class.java)

Slide 59

Slide 59 text

Testing easily with Koin ✅

Slide 60

Slide 60 text

repositories { jcenter() } dependencies { // Koin for Tests testImplementation ‘org.koin:koin-test:$version’ } Gradle Setup

Slide 61

Slide 61 text

Checking your modules ✔

Slide 62

Slide 62 text

class ModuleTest { @Test fun `check all definitions from coffeeMakerModule`() { startKoin { modules(coffeeMakerModule) }.checkModules() } }

Slide 63

Slide 63 text

✅ Koin extensions with JUnit & Mockito

Slide 64

Slide 64 text

class CoffeeMakerTest : KoinTest { val coffeeMaker: CoffeeMaker by inject() val heater: Heater by inject() @Test fun testHeaterIsTurnedOnAndThenOff() { startKoin { modules(coffeeMakerModule) } declareMock() given(heater.isHot()).will { true } coffeeMaker.brew() Mockito.verify(heater, times(1)).on() Mockito.verify(heater, times(1)).off() } }

Slide 65

Slide 65 text

class CoffeeMakerTest : KoinTest { val coffeeMaker: CoffeeMaker by inject() val heater: Heater by inject() @Test fun testHeaterIsTurnedOnAndThenOff() { startKoin { modules(coffeeMakerModule) } declareMock() given(heater.isHot()).will { true } coffeeMaker.brew() Mockito.verify(heater, times(1)).on() Mockito.verify(heater, times(1)).off() } }

Slide 66

Slide 66 text

class CoffeeMakerTest : KoinTest { val coffeeMaker: CoffeeMaker by inject() val heater: Heater by inject() @Test fun testHeaterIsTurnedOnAndThenOff() { startKoin { modules(coffeeMakerModule) } declareMock() given(heater.isHot()).will { true } coffeeMaker.brew() verify(heater, times(1)).on() verify(heater, times(1)).off() } }

Slide 67

Slide 67 text

Upgrading to Koin 2.0

Slide 68

Slide 68 text

Since first 1.0 version…

Slide 69

Slide 69 text

Simple DSL module, single, factory, get

Slide 70

Slide 70 text

Pragmatic API ✌ KoinComponent, get, inject

Slide 71

Slide 71 text

⚠ performances ⚠

Slide 72

Slide 72 text

@Sloydev - 450 definitions benchmark

Slide 73

Slide 73 text

No content

Slide 74

Slide 74 text

No content

Slide 75

Slide 75 text

* non linear startup / Kotlin overhead ** split modules startup if needed

Slide 76

Slide 76 text

Benchmarks https://github.com/InsertKoinIO/thermosiphon_dagger2koin https://github.com/Sloy/android-dependency-injection-performance https://github.com/arnaudgiuliani/android-dependency-injection-performance

Slide 77

Slide 77 text

Upgrade to Koin 2.0

Slide 78

Slide 78 text

No content

Slide 79

Slide 79 text

No content

Slide 80

Slide 80 text

No content

Slide 81

Slide 81 text

Some API Break - Simplified module DSL (no more inner modules) - Revamped Scope API - Context isolation & startKoin DSL (koinApplication) - Definition resolution (default & qualifiers)

Slide 82

Slide 82 text

http://bit.ly/ready4koin

Slide 83

Slide 83 text

More about Koin✨

Slide 84

Slide 84 text

- Container DSL - Qualifiers (string/type) - Global vs isolated context - Scope API - Android, Scopes & lifecycle - Injection Parameters - AndroidX - Koin for Java - Koin for Ktor

Slide 85

Slide 85 text

What is a scope? A scope is a context with fixed duration of time, in which an object exists. When the scope ends, any objects bound under that scope cannot be injected again.

Slide 86

Slide 86 text

Components’ lifetime Container Single Factory Scope Factory Factory

Slide 87

Slide 87 text

Components Container Data Repository View User Session View View

Slide 88

Slide 88 text

module { scope(named("Session")) { scoped { SessionData() } } } // create scope with id "session1" and qualifier "Session" val session1 = getKoin().createScope("session1",named("Session")) // get SessionData instance from scope "session1" val data : SessionData = session1.get() // close instances scope "session1" scope.close()

Slide 89

Slide 89 text

module { scope(named("Session")) { scoped { SessionData() } } } // create scope with id "session1" and qualifier "Session" val session1 = getKoin().createScope("session1",named("Session")) // get SessionData instance from scope "session1" val data : SessionData = session1.get() // close instances scope "session1" session1.close()

Slide 90

Slide 90 text

repositories { jcenter() } dependencies { // Koin for Android + Scope feature implementation ‘org.koin:koin-android-scope:$version’ } Gradle Setup

Slide 91

Slide 91 text

✅ Extensions for Android Lifecycle + Koin Scope

Slide 92

Slide 92 text

module { scope(named()) { scoped { MyPresenter() } } } class MyActivity : AppCompatActivity { val presenter : MyPresenter by currentScope.inject() } ** follow lifecycle * currentScope is provided by extension

Slide 93

Slide 93 text

version = “2.0.1" repositories { jcenter() } dependencies { // Kotlin Core features implementation "org.koin:koin-core:$version" // Android basic features implementation "org.koin:koin-android:$version" // Android basic + scope features implementation "org.koin:koin-android-scope:$version" // Android basic + scope + viewmodel features

Slide 94

Slide 94 text

dependencies { // Kotlin Core features implementation "org.koin:koin-core:$version" // Android basic features implementation "org.koin:koin-android:$version" // Android basic + scope features implementation "org.koin:koin-android-scope:$version" // Android basic + scope + viewmodel features implementation "org.koin:koin-android-viewmodel:$version" // Kotlin Checks + JUnit & Mock features testImplementation "org.koin:koin-test:$version" }

Slide 95

Slide 95 text

dependencies { // Kotlin Core features implementation "org.koin:koin-core:$version" // Android basic features implementation "org.koin:koin-android:$version" // Android basic + scope features implementation "org.koin:koin-android-scope:$version" // Android basic + scope + viewmodel features implementation "org.koin:koin-android-viewmodel:$version" // Kotlin Checks + JUnit & Mock features testImplementation "org.koin:koin-test:$version" }

Slide 96

Slide 96 text

dependencies { // Kotlin Core features implementation "org.koin:koin-core:$version" // Android basic features implementation "org.koin:koin-android:$version" // Android basic + scope features implementation "org.koin:koin-android-scope:$version" // Android basic + scope + viewmodel features implementation "org.koin:koin-android-viewmodel:$version" // Kotlin Checks + JUnit & Mock features testImplementation "org.koin:koin-test:$version" }

Slide 97

Slide 97 text

dependencies { // Kotlin Core features implementation "org.koin:koin-core:$version" // Android basic features implementation "org.koin:koin-android:$version" // Android basic + scope features implementation "org.koin:koin-android-scope:$version" // Android basic + scope + viewmodel features implementation "org.koin:koin-android-viewmodel:$version" // Kotlin Checks + JUnit & Mock features testImplementation "org.koin:koin-test:$version" }

Slide 98

Slide 98 text

dependencies { // Kotlin Core features implementation "org.koin:koin-core:$version" // Android basic features implementation "org.koin:koin-android:$version" // Android basic + scope features implementation "org.koin:koin-android-scope:$version" // Android basic + scope + viewmodel features implementation "org.koin:koin-android-viewmodel:$version" // Kotlin Checks + JUnit & Mock features testImplementation "org.koin:koin-test:$version" }

Slide 99

Slide 99 text

Before starting with Koin…

Slide 100

Slide 100 text

Try the getting started projects * look at code samples

Slide 101

Slide 101 text

No content

Slide 102

Slide 102 text

Read the quick references

Slide 103

Slide 103 text

Add it step by step to your project * start with small components

Slide 104

Slide 104 text

Check your modules

Slide 105

Slide 105 text

Review your software design

Slide 106

Slide 106 text

Keep it simple

Slide 107

Slide 107 text

Documentation reference for deeper look * or even in source code

Slide 108

Slide 108 text

No content

Slide 109

Slide 109 text

and …

Slide 110

Slide 110 text

StackOverflow, Github & Slack for any question

Slide 111

Slide 111 text

No content

Slide 112

Slide 112 text

@insertkoin_io koin-developers

Slide 113

Slide 113 text

Thanks! Early adopters, Community feedback, Conference, talks, meetups…

Slide 114

Slide 114 text

Spread the word!

Slide 115

Slide 115 text

insert-koin.io

Slide 116

Slide 116 text

Arnaud Giuliani - @arnogiu Thank you!