Slide 1

Slide 1 text

Android Architecture Design With Koin Arnaud Giuliani @arnogiu

Slide 2

Slide 2 text

Koin Project Lead Google Dev Expert - Kotlin @arnogiu Arnaud GIULIANI

Slide 3

Slide 3 text

Architecture Recipes With Koin

Slide 4

Slide 4 text

Koin Core Concepts πŸ› 

Slide 5

Slide 5 text

Just use Koin, what else?

Slide 6

Slide 6 text

class ClassA() class ClassB(val a : ClassA)

Slide 7

Slide 7 text

class ClassA() class ClassB(val a : ClassA) module { }

Slide 8

Slide 8 text

class ClassA() class ClassB(val a : ClassA) module { single { ClassB() } }

Slide 9

Slide 9 text

class ClassA() class ClassB(val a : ClassA) module { single { ClassA() } single { ClassB(???) } }

Slide 10

Slide 10 text

class ClassA() class ClassB(val a : ClassA) module { single { ClassA() } single { ClassB(get()) } } - Constructor injection con fi gured via Koin DSL - Instances are called by Koin Container

Slide 11

Slide 11 text

class MyApp { val classB : ClassB }

Slide 12

Slide 12 text

class MyApp : KoinComponent { val classB : ClassB }

Slide 13

Slide 13 text

class MyApp : KoinComponent { val classB by inject() } - Use the Koin API to retrieve a dependency - Host class is not created by Koin MyApp().classB

Slide 14

Slide 14 text

Classical MVP-MVVM 🎯

Slide 15

Slide 15 text

https:/ /github.com/InsertKoinIO/architecture-samples/tree/dev-koin https:/ /github.com/android/architecture-samples

Slide 16

Slide 16 text

Tasks Repository Tasks DataSource Tasks Local DataSource TasksViewModel TasksActivity Tasks Database

Slide 17

Slide 17 text

Since Koin 3.x! // Android implementation "io.insert-koin:koin-android:$koin_version" implementation "io.insert-koin:koin-androidx-viewmodel:$koin_version" implementation "io.insert-koin:koin-androidx-scope:$koin_version" implementation "io.insert-koin:koin-androidx-fragment:$koin_version"

Slide 18

Slide 18 text

Tasks DataSource class TasksRemoteDataSource : TasksDataSource // Koin DSL single { TasksRemoteDataSource() }

Slide 19

Slide 19 text

Tasks DataSource Tasks Local DataSource class TasksRemoteDataSource : TasksDataSource class TasksLocalDataSource( private val tasksDao: TasksDao, private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO ) : TasksDataSource // Koin DSL single { TasksRemoteDataSource() } single { TasksLocalDataSource(get(),get()) }

Slide 20

Slide 20 text

Tasks DataSource Tasks Local DataSource class TasksRemoteDataSource : TasksDataSource class TasksLocalDataSource( private val tasksDao: TasksDao, private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO ) : TasksDataSource // Koin DSL single(named("RemoteDS")) { TasksRemoteDataSource() } single(named("LocalDS")) { TasksLocalDataSource(get(),get()) }

Slide 21

Slide 21 text

class DefaultTasksRepository( private val tasksRemoteDataSource: TasksDataSource, private val tasksLocalDataSource: TasksDataSource, private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO ) : TasksRepository // Koin DSL single { DefaultTasksRepository( get(), get(), get() ) } Tasks Repository

Slide 22

Slide 22 text

class DefaultTasksRepository( private val tasksRemoteDataSource: TasksDataSource, private val tasksLocalDataSource: TasksDataSource, private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO ) : TasksRepository // Koin DSL single { DefaultTasksRepository( get(named("RemoteDS")), get(named("LocalDS")), get() ) } Tasks Repository

Slide 23

Slide 23 text

// Koin DSL single { Room.databaseBuilder( androidContext().applicationContext, ToDoDatabase::class.java, "Tasks.db" ).build() } single { get().taskDao() } Tasks Database

Slide 24

Slide 24 text

// Koin DSL single { Room.databaseBuilder( androidContext().applicationContext, ToDoDatabase::class.java, "Tasks.db" ).build() } single { get().taskDao() } Tasks Database

Slide 25

Slide 25 text

class TasksViewModel( private val tasksRepository: TasksRepository, private val savedStateHandle: SavedStateHandle ) : ViewModel() // Koin DSL viewModel { TasksViewModel(get(),get()) } TasksViewModel

Slide 26

Slide 26 text

// by ViewModel() in Activity & Fragment class TasksActivity : AppCompatActivity() { // also inject SavedStateHandle val viewModel : TasksViewModel by viewModel() } TasksActivity

Slide 27

Slide 27 text

Tasks Repository Tasks DataSource Tasks Local DataSource TasksActivity Tasks Database

Slide 28

Slide 28 text

Tasks Repository Tasks DataSource Tasks Local DataSource TasksPresenter TasksActivity Tasks Database

Slide 29

Slide 29 text

class TasksPresenter( private val tasksRepository: TasksRepository, private val view: TasksView ) // Koin DSL - Parameter injection for Presenter factory { TasksPresenter(get(), ??? ) } TasksViewPresenter

Slide 30

Slide 30 text

class TasksPresenter( private val tasksRepository: TasksRepository, private val view: TasksView ) // Koin DSL - Parameter injection for Presenter factory { (view: TasksView) -> TasksPresenter(get(),view) } TasksViewPresenter

Slide 31

Slide 31 text

class TasksActivity : TasksView, AppCompatActivity() { // inject itself as a view val presenter : TasksPresenter by inject { parametersOf(this) } } TasksView

Slide 32

Slide 32 text

val appModule = module { // Presentation Layers viewModel { TasksViewModel(get(),get()) } factory { (view: TasksView) -> TasksPresenter(get(),view) } // Repository single { DefaultTasksRepository(get(named("RemoteDS")),get(named("LocalDS")),get()) } // Data Sources single(named("RemoteDS")) { TasksRemoteDataSource() } single(named("LocalDS")) { TasksLocalDataSource(get(),get()) } // Room single { Room.databaseBuilder(...).build() } single { get().taskDao() } }

Slide 33

Slide 33 text

- Layers, features … - Granularity & Testability Organising Modules? πŸ€”

Slide 34

Slide 34 text

val dataModule = module { } val uiModule = module { } val appModule = dataModule + uiModule startKoin { modules(appModule) }

Slide 35

Slide 35 text

Clean Architecture πŸ—

Slide 36

Slide 36 text

No content

Slide 37

Slide 37 text

Tasks Repository Tasks DataSource Tasks Local DataSource TasksViewModel TasksActivity Tasks Database

Slide 38

Slide 38 text

Tasks Repository Tasks DataSource Tasks Local DataSource TasksViewModel TasksActivity Tasks Database GetTaskUseCase

Slide 39

Slide 39 text

class TasksViewModel( private val getTaskUseCase: GetTaskUseCase, private val savedStateHandle: SavedStateHandle ) : ViewModel() // Koin DSL viewModel { TasksViewModel(get(),get()) } TasksViewModel

Slide 40

Slide 40 text

class GetTaskUseCase( private val tasksRepository: TasksRepository ) // Koin DSL // Don’t hold instance - ensure it’s stateless factory { GetTaskUseCase(get()) } GetTaskUseCase

Slide 41

Slide 41 text

val appModule = module { // Presentation Layers viewModel { TasksViewModel(get(),get()) } factory { (view: TasksView) -> TasksPresenter(get(),view) } // Repository single { DefaultTasksRepository(get(named("RemoteDS")),get(named("LocalDS")),get()) } // Data Sources single(named("RemoteDS")) { TasksRemoteDataSource() } single(named("LocalDS")) { TasksLocalDataSource(get(),get()) } // Room single { Room.databaseBuilder(...).build() } single { get().taskDao() } }

Slide 42

Slide 42 text

val appModule = module { // Presentation Layers viewModel { TasksViewModel(get(),get()) } factory { (view: TasksView) -> TasksPresenter(get(),view) } // Usecase factory { GetTaskUseCase(get()) } // Repository single { DefaultTasksRepository(get(named("RemoteDS")),get(named("LocalDS")),get()) } // Data Sources single(named("RemoteDS")) { TasksRemoteDataSource() } single(named("LocalDS")) { TasksLocalDataSource(get(),get()) } // Room single { Room.databaseBuilder(...).build() } single { get().taskDao() } }

Slide 43

Slide 43 text

Let’s wrap up πŸ‘

Slide 44

Slide 44 text

Tasks Repository Tasks DataSource Tasks Local DataSource TasksPresenter TasksActivity Tasks Database TasksViewModel Koin API factory or viewModel single single single

Slide 45

Slide 45 text

Koin API factory or viewModel single single single Tasks Repository Tasks DataSource Tasks Local DataSource TasksViewModel TasksActivity Tasks Database GetTaskUseCase TasksPresenter factory

Slide 46

Slide 46 text

Koin API factory or viewModel single single single Repository DataSource Local DataSource ViewModel Android UI Database UseCase / Interactor Presenter factory

Slide 47

Slide 47 text

Working with Scopes πŸš‘

Slide 48

Slide 48 text

Why do we need a scope? πŸ“¦ keep instances ⏱ specific time

Slide 49

Slide 49 text

What about Android scope? We maintain instances for the duration of an Android component lifecycle.

Slide 50

Slide 50 text

Follow the Android Components

Slide 51

Slide 51 text

class MyActivity : AppCompatActivity() { val myPresenter : MyPresenter by inject() } // Koin DSL factory { MyPresenter(...) } πŸ’₯

Slide 52

Slide 52 text

Android Scope

Slide 53

Slide 53 text

class MyActivity : ScopeActivity() { // Resolved in MyActivity's scope val myPresenter : MyPresenter by inject() val myAdapter : MyAdapter by inject() } // Koin DSL scope { scoped { MyPresenter() } scoped { MyAdapter(get()) } } class MyAdapter(val presenter : Mypresenter) πŸ’₯ ScopeActivity use Activity’s scope by default

Slide 54

Slide 54 text

ViewModel

Slide 55

Slide 55 text

class MyActivity : Activity() { val myViewModel : MyViewModel by viewModel() } // Koin DSL viewModel { MyViewModel() } β™» class MyViewModel() : ViewModel()

Slide 56

Slide 56 text

class MyActivity : ScopeActivity() { // Override current scope to Activity's scope by default override val scope : Scope by activityRetainedScope() val myViewModel : MyViewModel by viewModel() val myAdapter : MyAdapter by inject() } All components are backed By ViewModel βœ…

Slide 57

Slide 57 text

Navigation Graph ViewModel

Slide 58

Slide 58 text

// Android Nav implementation β€œio.insert-koin:koin-androidx-navigation:$koin_version"

Slide 59

Slide 59 text

class NavViewModel() : ViewModel() // Koin DSL viewModel { NavViewModel() } NavViewModel

Slide 60

Slide 60 text

class MyFragment : Fragment() { val navViewModel : NavViewModel by koinNavGraphViewModel(R.id.nav_graph) } MyFragment Nav Graph

Slide 61

Slide 61 text

Scopes - Wrap up πŸ‘

Slide 62

Slide 62 text

// Koin DSL scope { scoped { MyPresenter() } scoped { MyAdapter(get()) } } Define a scope class MyActivity : ScopeActivity() { } interface AndroidScopeComponent { override val scope : Scope by activityScope() } Use a scope with Android API

Slide 63

Slide 63 text

// create scope getKoin().createScope(...) // ... get it elsewhere val scope = getKoin().getScope(...) scope.get<>() Get the Scope API class MyFragment : Fragment() { val navViewModel : NavViewModel by koinNavGraphViewModel(R.id.nav_graph) } ViewModel scoped to Nav Graph

Slide 64

Slide 64 text

Koin API factory or viewModel single single single Repository DataSource Local DataSource ViewModel Android UI Database Presenter

Slide 65

Slide 65 text

Koin API factory, viewModel or Scope single single single Repository DataSource Local DataSource ViewModel Android UI Database Presenter

Slide 66

Slide 66 text

Keep your Koin con fi g simple ✨

Slide 67

Slide 67 text

Less Code = Less Bugs Chet Haase

Slide 68

Slide 68 text

New Architectures! πŸ¦„

Slide 69

Slide 69 text

Jetpack Compose πŸ¦„

Slide 70

Slide 70 text

Injecting in Compose

Slide 71

Slide 71 text

// Android Nav implementation β€œio.insert-koin:koin-androidx-compose:$koin_version”

Slide 72

Slide 72 text

// Retrieving instance in a composable @Composable fun App() { val myService = get() // or by inject() } Getting instances in a Composable // Default parameter value @Composable fun App(myService: MyService = get()) { }

Slide 73

Slide 73 text

// Retrieving ViewModel in a composable @Composable fun App() { val myViewModel = getViewModel() // or by viewModel() } Getting ViewModel in a Composable // Default parameter value @Composable fun App(myViewModel: MyViewModel = getViewModel()) { }

Slide 74

Slide 74 text

https:/ /github.com/InsertKoinIO/compose-samples Jetpack Compose Samples

Slide 75

Slide 75 text

Koin API factory or viewModel single single single Repository DataSource Local DataSource ViewModel Android UI Database Presenter

Slide 76

Slide 76 text

Koin API factory or viewModel single single single Repository DataSource Local DataSource ViewModel Composable Database Presenter

Slide 77

Slide 77 text

Dependency Injection For Kotlin Multiplatform ⭐

Slide 78

Slide 78 text

No content

Slide 79

Slide 79 text

Repository DataSource Local DataSource Presentation Android UI Database

Slide 80

Slide 80 text

Repository DataSource Local DataSource Native UI (Android, iOS) Database Presentation

Slide 81

Slide 81 text

Shared Kotlin Common Repository DataSource Local DataSource Native UI (Android, iOS) Database Presentation

Slide 82

Slide 82 text

People In Space https:/ /github.com/joreilly/PeopleInSpace

Slide 83

Slide 83 text

No content

Slide 84

Slide 84 text

Android App Common Code iOS App Backend & web app

Slide 85

Slide 85 text

Common Code

Slide 86

Slide 86 text

Common DI Modules

Slide 87

Slide 87 text

Kermit Logging Json Serialization Ktor Http Client + API Component Data Repository

Slide 88

Slide 88 text

Json Serialization Ktor Http Client + API Component Data Repository Kermit Logging => Data Storage with SQLDelight

Slide 89

Slide 89 text

No content

Slide 90

Slide 90 text

JVM Impl iOS Impl

Slide 91

Slide 91 text

val appModule = module { viewModel { PeopleInSpaceViewModel(get()) } } ViewModel -> Repository

Slide 92

Slide 92 text

Koin API factory or viewModel single single single Kotlin Multiplatform API πŸš€ Repository DataSource Local DataSource Native UI (Android, iOS) Database Presentation

Slide 93

Slide 93 text

New features! 🌈

Slide 94

Slide 94 text

New Feature Proposal ✨

Slide 95

Slide 95 text

Limits of Koin DSL 🀨

Slide 96

Slide 96 text

Kotlin Compiler Plugin! πŸ› 

Slide 97

Slide 97 text

KSP Powered πŸ”₯ πŸ‘‰ Generate Koin stu ff DSL for you https:/ /github.com/google/ksp

Slide 98

Slide 98 text

Koin Semantic πŸ€”

Slide 99

Slide 99 text

koin_annotations_version = β€œ1.0.0-beta-2” implementation "io.insert-koin:koin-annotations:$koin_ksp_version" ksp "io.insert-koin:koin-ksp-compiler:$koin_ksp_version"

Slide 100

Slide 100 text

Tasks Repository Tasks DataSource Tasks Local DataSource TasksViewModel TasksActivity Tasks Database

Slide 101

Slide 101 text

De fi nitions

Slide 102

Slide 102 text

Tasks DataSource class TasksRemoteDataSource : TasksDatasource

Slide 103

Slide 103 text

Tasks DataSource @Single class TasksRemoteDataSource : TasksDatasource

Slide 104

Slide 104 text

Tasks DataSource Tasks Local DataSource class TasksRemoteDataSource : TasksDatasource class TasksLocalDataSource( private val tasksDao: TasksDao, private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO ) : TasksDatasource

Slide 105

Slide 105 text

Tasks DataSource Tasks Local DataSource @Single class TasksRemoteDataSource : TasksDatasource @Single class TasksLocalDataSource( private val tasksDao: TasksDao, private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO ) : TasksDatasource

Slide 106

Slide 106 text

Tasks DataSource Tasks Local DataSource @Single @Named("RemoteDS") class TasksRemoteDataSource : TasksDatasource @Single @Named("LocalDS") class TasksLocalDataSource( private val tasksDao: TasksDao, private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO ) : TasksDatasource

Slide 107

Slide 107 text

class DefaultTasksRepository( private val tasksRemoteDataSource: TasksDataSource, private val tasksLocalDataSource: TasksDataSource, private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO ) : TasksRepository Tasks Repository

Slide 108

Slide 108 text

@Single class DefaultTasksRepository( private val tasksRemoteDataSource: TasksDataSource, private val tasksLocalDataSource: TasksDataSource, private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO ) : TasksRepository Tasks Repository

Slide 109

Slide 109 text

@Single class DefaultTasksRepository( @Named("RemoteDS") private val tasksRemoteDataSource: TasksDataSource, @Named("LocalDS") private val tasksLocalDataSource: TasksDataSource, private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO ) : TasksRepository Tasks Repository

Slide 110

Slide 110 text

class TasksViewModel( private val tasksRepository: TasksRepository, private val savedStateHandle: SavedStateHandle ) : ViewModel() TasksViewModel

Slide 111

Slide 111 text

@KoinViewModel class TasksViewModel( private val tasksRepository: TasksRepository, private val savedStateHandle: SavedStateHandle ) : ViewModel() TasksViewModel

Slide 112

Slide 112 text

// by ViewModel() in Activity & Fragment class TasksActivity : AppCompatActivity() { // also inject SavedStateHandle val viewModel : TasksViewModel by viewModel() } TasksActivity

Slide 113

Slide 113 text

class TasksPresenter( private val tasksRepository: TasksRepository, private val view: TasksView ) TasksViewPresenter

Slide 114

Slide 114 text

@Factory class TasksPresenter( private val tasksRepository: TasksRepository, private val view: TasksView ) TasksViewPresenter

Slide 115

Slide 115 text

@Factory class TasksPresenter( private val tasksRepository: TasksRepository, @InjectedParam private val view: TasksView ) TasksViewPresenter

Slide 116

Slide 116 text

class TasksActivity : TasksView, AppCompatActivity() { // inject itself as a view val presenter : TasksPresenter by inject { parametersOf(this) } } TasksView

Slide 117

Slide 117 text

Tasks Repository Tasks DataSource Tasks Local DataSource TasksPresenter TasksActivity Tasks Database TasksViewModel Koin API @Factory or @koinViewModel @Single @Single @Single

Slide 118

Slide 118 text

Modules

Slide 119

Slide 119 text

Gathering definitions into Modules @Module class AppModule

Slide 120

Slide 120 text

Gathering definitions into Modules @Module @ComponentScan("com.my.app") class AppModule

Slide 121

Slide 121 text

import org.koin.ksp.generated.* startKoin { modules( // Class Module AppModule().module ) } Starting with Modules

Slide 122

Slide 122 text

import org.koin.ksp.generated.* startKoin { modules( // DSL Module otherDSLModule, // Class Module AppModule().module ) } Mix Classes & DSL Modules

Slide 123

Slide 123 text

@Module class DatabaseModule { @Single fun taskDao(db : ToDoDatabase) : TaskDao = db.taskDao() } Tasks Database

Slide 124

Slide 124 text

@Module class DatabaseModule { @Single fun taskDao(db : ToDoDatabase) : TaskDao = db.taskDao() } Tasks Database

Slide 125

Slide 125 text

Kotlin Compiler Plugin ☒ Code Generation

Slide 126

Slide 126 text

Fast Compilation Time 🏎 ~2 sec - 1000 components

Slide 127

Slide 127 text

Easy to Debug πŸš’ Generated Koin DSL modules

Slide 128

Slide 128 text

DSL and Annotations Choose what is best for you πŸ‘

Slide 129

Slide 129 text

πŸ‘‰ https:/ /github.com/InsertKoinIO/ koin-annotations

Slide 130

Slide 130 text

What’s Next? πŸš€

Slide 131

Slide 131 text

Koin Downloads Volume πŸ“¦ πŸ“¦ 5M Downloads in 2020 9M Downloads in 2021 πŸš€βœ¨πŸŒˆ 2022: Trends of 5M / Month

Slide 132

Slide 132 text

πŸ—“ Roadmap 2022 Structured Release Cycle (~6months) - Fix Version (bug fi xes, fi x updates) - Minor Version (deprecations, minor features…) - Major Version (hard breaking …)

Slide 133

Slide 133 text

πŸ—“ Roadmap 2022 Q4 Releases: Koin 3.3, Koin for Compose End of Track: Koin 3.1 Q2 Releases: Koin 3.2, Koin Annotations 1.0 πŸ‘‰ New DSL, Module includes …

Slide 134

Slide 134 text

⚠ End of support for Koin 2.X insert-koin.io/docs/migration/migrate

Slide 135

Slide 135 text

// Latest version! koin_version = "3.1.6" // Current beta koin_version = "3.2.0-beta-1"

Slide 136

Slide 136 text

Koin β€” open source and community driven! πŸ‘

Slide 137

Slide 137 text

@insertkoin_io insert-koin.io

Slide 138

Slide 138 text

But need of real company support

Slide 139

Slide 139 text

Long Term Support Releases & Professional Services

Slide 140

Slide 140 text

🌐 kotzilla.io @kotzilla_io 🌐 blog.kotzilla.io

Slide 141

Slide 141 text

#goodies πŸ™Œ

Slide 142

Slide 142 text

Arnaud Giuliani @arnogiu Thank you! πŸ™‚