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

Android Architecture Design With Koin

Android Architecture Design With Koin

Koin (https://insert-koin.io) is a pragmatic dependency injection framework for Kotlin/Android developers, well known for its ease of use. In this session, I propose to see together how we can structure our components, for the following topics:
* Classical Components in MVP, MVVM
* Dealing with Scopes
* Jetpack Navigation
* Jetpack Compose
* Integrating with MultiPlatform

And bonus, we will talk about new features.

E02cd1d0fc5c51ac491b484a258a63a6?s=128

Arnaud GIULIANI

April 30, 2022
Tweet

More Decks by Arnaud GIULIANI

Other Decks in Programming

Transcript

  1. Android Architecture Design With Koin Arnaud Giuliani @arnogiu

  2. Koin Project Lead Google Dev Expert - Kotlin @arnogiu Arnaud

    GIULIANI
  3. Architecture Recipes With Koin

  4. Koin Core Concepts 🛠

  5. Just use Koin, what else?

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

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

  8. class ClassA() class ClassB(val a : ClassA) module { single

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

    { ClassA() } single { ClassB(???) } }
  10. 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
  11. class MyApp { val classB : ClassB }

  12. class MyApp : KoinComponent { val classB : ClassB }

  13. class MyApp : KoinComponent { val classB by inject<ClassB>() }

    - Use the Koin API to retrieve a dependency - Host class is not created by Koin MyApp().classB
  14. Classical MVP-MVVM 🎯

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

  16. Tasks Repository Tasks DataSource Tasks Local DataSource TasksViewModel TasksActivity Tasks

    Database
  17. 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"
  18. Tasks DataSource class TasksRemoteDataSource : TasksDataSource // Koin DSL single<TasksDataSource>

    { TasksRemoteDataSource() }
  19. Tasks DataSource Tasks Local DataSource class TasksRemoteDataSource : TasksDataSource class

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

    TasksLocalDataSource( private val tasksDao: TasksDao, private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO ) : TasksDataSource // Koin DSL single<TasksDataSource>(named("RemoteDS")) { TasksRemoteDataSource() } single<TasksDataSource>(named("LocalDS")) { TasksLocalDataSource(get(),get()) }
  21. class DefaultTasksRepository( private val tasksRemoteDataSource: TasksDataSource, private val tasksLocalDataSource: TasksDataSource,

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

    private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO ) : TasksRepository // Koin DSL single<TasksRepository> { DefaultTasksRepository( get(named("RemoteDS")), get(named("LocalDS")), get() ) } Tasks Repository
  23. // Koin DSL single { Room.databaseBuilder( androidContext().applicationContext, ToDoDatabase::class.java, "Tasks.db" ).build()

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

    } single { get<ToDoDatabase>().taskDao() } Tasks Database
  25. class TasksViewModel( private val tasksRepository: TasksRepository, private val savedStateHandle: SavedStateHandle

    ) : ViewModel() // Koin DSL viewModel { TasksViewModel(get(),get()) } TasksViewModel
  26. // by ViewModel() in Activity & Fragment class TasksActivity :

    AppCompatActivity() { // also inject SavedStateHandle val viewModel : TasksViewModel by viewModel() } TasksActivity
  27. Tasks Repository Tasks DataSource Tasks Local DataSource TasksActivity Tasks Database

  28. Tasks Repository Tasks DataSource Tasks Local DataSource TasksPresenter TasksActivity Tasks

    Database
  29. class TasksPresenter( private val tasksRepository: TasksRepository, private val view: TasksView

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

    ) // Koin DSL - Parameter injection for Presenter factory { (view: TasksView) -> TasksPresenter(get(),view) } TasksViewPresenter
  31. class TasksActivity : TasksView, AppCompatActivity() { // inject itself as

    a view val presenter : TasksPresenter by inject { parametersOf(this) } } TasksView
  32. val appModule = module { // Presentation Layers viewModel {

    TasksViewModel(get(),get()) } factory { (view: TasksView) -> TasksPresenter(get(),view) } // Repository single<TasksRepository> { DefaultTasksRepository(get(named("RemoteDS")),get(named("LocalDS")),get()) } // Data Sources single<TasksDataSource>(named("RemoteDS")) { TasksRemoteDataSource() } single<TasksDataSource>(named("LocalDS")) { TasksLocalDataSource(get(),get()) } // Room single { Room.databaseBuilder(...).build() } single { get<ToDoDatabase>().taskDao() } }
  33. - Layers, features … - Granularity & Testability Organising Modules?

    🤔
  34. val dataModule = module { } val uiModule = module

    { } val appModule = dataModule + uiModule startKoin { modules(appModule) }
  35. Clean Architecture 🏗

  36. None
  37. Tasks Repository Tasks DataSource Tasks Local DataSource TasksViewModel TasksActivity Tasks

    Database
  38. Tasks Repository Tasks DataSource Tasks Local DataSource TasksViewModel TasksActivity Tasks

    Database GetTaskUseCase
  39. class TasksViewModel( private val getTaskUseCase: GetTaskUseCase, private val savedStateHandle: SavedStateHandle

    ) : ViewModel() // Koin DSL viewModel { TasksViewModel(get(),get()) } TasksViewModel
  40. class GetTaskUseCase( private val tasksRepository: TasksRepository ) // Koin DSL

    // Don’t hold instance - ensure it’s stateless factory { GetTaskUseCase(get()) } GetTaskUseCase
  41. val appModule = module { // Presentation Layers viewModel {

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

    TasksViewModel(get(),get()) } factory { (view: TasksView) -> TasksPresenter(get(),view) } // Usecase factory { GetTaskUseCase(get()) } // Repository single<TasksRepository> { DefaultTasksRepository(get(named("RemoteDS")),get(named("LocalDS")),get()) } // Data Sources single<TasksDataSource>(named("RemoteDS")) { TasksRemoteDataSource() } single<TasksDataSource>(named("LocalDS")) { TasksLocalDataSource(get(),get()) } // Room single { Room.databaseBuilder(...).build() } single { get<ToDoDatabase>().taskDao() } }
  43. Let’s wrap up 👍

  44. Tasks Repository Tasks DataSource Tasks Local DataSource TasksPresenter TasksActivity Tasks

    Database TasksViewModel Koin API factory or viewModel single single single
  45. Koin API factory or viewModel single single single Tasks Repository

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

    Local DataSource ViewModel Android UI Database UseCase / Interactor Presenter factory
  47. Working with Scopes 🚑

  48. Why do we need a scope? 📦 keep instances ⏱

    specific time
  49. What about Android scope? We maintain instances for the duration

    of an Android component lifecycle.
  50. Follow the Android Components

  51. class MyActivity : AppCompatActivity() { val myPresenter : MyPresenter by

    inject() } // Koin DSL factory { MyPresenter(...) } 💥
  52. Android Scope

  53. class MyActivity : ScopeActivity() { // Resolved in MyActivity's scope

    val myPresenter : MyPresenter by inject() val myAdapter : MyAdapter by inject() } // Koin DSL scope<MyActivity> { scoped { MyPresenter() } scoped { MyAdapter(get()) } } class MyAdapter(val presenter : Mypresenter) 💥 ScopeActivity use Activity’s scope by default
  54. ViewModel

  55. class MyActivity : Activity() { val myViewModel : MyViewModel by

    viewModel() } // Koin DSL viewModel { MyViewModel() } ♻ class MyViewModel() : ViewModel()
  56. 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 ✅
  57. Navigation Graph ViewModel

  58. // Android Nav implementation “io.insert-koin:koin-androidx-navigation:$koin_version"

  59. class NavViewModel() : ViewModel() // Koin DSL viewModel { NavViewModel()

    } NavViewModel
  60. class MyFragment : Fragment() { val navViewModel : NavViewModel by

    koinNavGraphViewModel(R.id.nav_graph) } MyFragment Nav Graph
  61. Scopes - Wrap up 👍

  62. // Koin DSL scope<MyActivity> { 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
  63. // 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
  64. Koin API factory or viewModel single single single Repository DataSource

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

    DataSource Local DataSource ViewModel Android UI Database Presenter
  66. Keep your Koin con fi g simple ✨

  67. Less Code = Less Bugs Chet Haase

  68. New Architectures! 🦄

  69. Jetpack Compose 🦄

  70. Injecting in Compose

  71. // Android Nav implementation “io.insert-koin:koin-androidx-compose:$koin_version”

  72. // Retrieving instance in a composable @Composable fun App() {

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

    val myViewModel = getViewModel<MyViewModel>() // or by viewModel<MyViewModel>() } Getting ViewModel in a Composable // Default parameter value @Composable fun App(myViewModel: MyViewModel = getViewModel()) { }
  74. https:/ /github.com/InsertKoinIO/compose-samples Jetpack Compose Samples

  75. Koin API factory or viewModel single single single Repository DataSource

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

    Local DataSource ViewModel Composable Database Presenter
  77. Dependency Injection For Kotlin Multiplatform ⭐

  78. None
  79. Repository DataSource Local DataSource Presentation Android UI Database

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

  81. Shared Kotlin Common Repository DataSource Local DataSource Native UI (Android,

    iOS) Database Presentation
  82. People In Space https:/ /github.com/joreilly/PeopleInSpace

  83. None
  84. Android App Common Code iOS App Backend & web app

  85. Common Code

  86. Common DI Modules

  87. Kermit Logging Json Serialization Ktor Http Client + API Component

    Data Repository
  88. Json Serialization Ktor Http Client + API Component Data Repository

    Kermit Logging => Data Storage with SQLDelight
  89. None
  90. JVM Impl iOS Impl

  91. val appModule = module { viewModel { PeopleInSpaceViewModel(get()) } }

    ViewModel -> Repository
  92. Koin API factory or viewModel single single single Kotlin Multiplatform

    API 🚀 Repository DataSource Local DataSource Native UI (Android, iOS) Database Presentation
  93. New features! 🌈

  94. New Feature Proposal ✨

  95. Limits of Koin DSL 🤨

  96. Kotlin Compiler Plugin! 🛠

  97. KSP Powered 🔥 👉 Generate Koin stu ff DSL for

    you https:/ /github.com/google/ksp
  98. Koin Semantic 🤔

  99. 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"

  100. Tasks Repository Tasks DataSource Tasks Local DataSource TasksViewModel TasksActivity Tasks

    Database
  101. De fi nitions

  102. Tasks DataSource class TasksRemoteDataSource : TasksDatasource

  103. Tasks DataSource @Single class TasksRemoteDataSource : TasksDatasource

  104. Tasks DataSource Tasks Local DataSource class TasksRemoteDataSource : TasksDatasource class

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

    @Single class TasksLocalDataSource( private val tasksDao: TasksDao, private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO ) : TasksDatasource
  106. 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
  107. class DefaultTasksRepository( private val tasksRemoteDataSource: TasksDataSource, private val tasksLocalDataSource: TasksDataSource,

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

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

    val tasksLocalDataSource: TasksDataSource, private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO ) : TasksRepository Tasks Repository
  110. class TasksViewModel( private val tasksRepository: TasksRepository, private val savedStateHandle: SavedStateHandle

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

    SavedStateHandle ) : ViewModel() TasksViewModel
  112. // by ViewModel() in Activity & Fragment class TasksActivity :

    AppCompatActivity() { // also inject SavedStateHandle val viewModel : TasksViewModel by viewModel() } TasksActivity
  113. class TasksPresenter( private val tasksRepository: TasksRepository, private val view: TasksView

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

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

    view: TasksView ) TasksViewPresenter
  116. class TasksActivity : TasksView, AppCompatActivity() { // inject itself as

    a view val presenter : TasksPresenter by inject { parametersOf(this) } } TasksView
  117. Tasks Repository Tasks DataSource Tasks Local DataSource TasksPresenter TasksActivity Tasks

    Database TasksViewModel Koin API @Factory or @koinViewModel @Single @Single @Single
  118. Modules

  119. Gathering definitions into Modules @Module class AppModule

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

  121. import org.koin.ksp.generated.* startKoin { modules( // Class Module AppModule().module )

    } Starting with Modules
  122. import org.koin.ksp.generated.* startKoin { modules( // DSL Module otherDSLModule, //

    Class Module AppModule().module ) } Mix Classes & DSL Modules
  123. @Module class DatabaseModule { @Single fun taskDao(db : ToDoDatabase) :

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

    TaskDao = db.taskDao() } Tasks Database
  125. Kotlin Compiler Plugin ☢ Code Generation

  126. Fast Compilation Time 🏎 ~2 sec - 1000 components

  127. Easy to Debug 🚒 Generated Koin DSL modules

  128. DSL and Annotations Choose what is best for you 👍

  129. 👉 https:/ /github.com/InsertKoinIO/ koin-annotations

  130. What’s Next? 🚀

  131. Koin Downloads Volume 📦 📦 5M Downloads in 2020 9M

    Downloads in 2021 🚀✨🌈 2022: Trends of 5M / Month
  132. 🗓 Roadmap 2022 Structured Release Cycle (~6months) - Fix Version

    (bug fi xes, fi x updates) - Minor Version (deprecations, minor features…) - Major Version (hard breaking …)
  133. 🗓 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 …
  134. ⚠ End of support for Koin 2.X insert-koin.io/docs/migration/migrate

  135. // Latest version! koin_version = "3.1.6" // Current beta koin_version

    = "3.2.0-beta-1"
  136. Koin — open source and community driven! 👍

  137. @insertkoin_io insert-koin.io

  138. But need of real company support

  139. Long Term Support Releases & Professional Services

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

  141. #goodies 🙌

  142. Arnaud Giuliani @arnogiu Thank you! 🙂