Dependency Injection the masterpiece on Android

Dependency Injection the masterpiece on Android

The goal was to talk about dependency injection, show advantages, disadvantages and how it works. (meetup BsAs Argentina November 2019 & devfest CDMX December 2019 )

9f523401a845a29c93ff73cde4c3db2b?s=128

Erik Jhordan Rey

December 14, 2019
Tweet

Transcript

  1. Erik Jhordan Rey @ErikJhordan_Rey Dependency Injection the masterpiece on Android

  2. Inheritance or Composition?

  3. What is a Dependency?

  4. A dependency is a coupling between two modules of our

    code, usually because one of them uses the other to do something.
  5. Tennis Match Player 1 Player 2

  6. class Player1 { fun serve() { print("\n Player 1 -

    Serve") } } class Player2 { fun drive() { print("\n Player 2 - Drive") } }
  7. class TennisMatch { private val player1 = Player1() private val

    player2 = Player2() fun start() { print("Start Match") player1.serve() player2.drive() } } fun main() { val match = TennisMatch() match.start() } Without dependency Injection
  8. Common Software Smells • Architecture based on Inheritance • A

    lot Coupled Code • Difficult to add new functionality • Infinite levels of Inheritance • Extends non abstract class • Base classes with hundreds of lines code.
  9. Inversion of Control (IoC)

  10. The natural evolution of the structured and imperative programming and

    the usage of reusable frameworks.
  11. Dependency Injection

  12. Software Design Pattern used to facilitate the usage of Dependency

    Inversion. It consists of passing dependencies via constructor or setter to extract the task of creating modules out from other modules. Objects are instantiated somewhere else and passed as constructor attributes when creating the current object.
  13. Tennis Match Player 1 Player 2

  14. class TennisMatch(private val player1: Player1, private val player2: Player2) {

    fun start() { print("Start Match") player1.serve() player2.drive() } } fun main() { val player1 = Player1() val player2 = Player2() val match = TennisMatch(player1, player2) match.start() } Constructor Injection
  15. class TennisMatch { lateinit var player1: Player1 lateinit var player2:

    Player2 fun start() { print("Start Match") player1.serve() player2.drive() } } fun main() { val match = TennisMatch() match.player1 = Player1() match.player2 = Player2() match.start() } Field or Setter Injection
  16. Dependency Inversion

  17. Depend upon abstractions. Do not depend upon concrete classes

  18. interface TennisPlayer { fun shot() } class Player1 : TennisPlayer

    { override fun shot() { print("\n Player 1 - Serve") } } class Player2 : TennisPlayer { override fun shot() { print("\n Player 2 - Drive") } }
  19. class TennisMatch(private val tennisPlayer1: TennisPlayer, private val tennisPlayer2: TennisPlayer) {

    fun start() { print("Start Match") tennisPlayer1.shot() tennisPlayer2.shot() } } fun main() { val player1 = Player1() val player2 = Player2() val match = TennisMatch(player1, player2) match.start() }
  20. Dependency Injection Advantages

  21. • Since dependencies can be injected and configured externally we

    can reuse those components. • We can just change the implementation of any object without having to make a lot of changes in our codebase, since that object instantiation resides in one place isolated and decoupled. • Detect smells related to class dependencies • Dependencies can be injected into a component: it is possible to inject mock implementations of these dependencies which makes testing easier.
  22. Dependency Injection Smells

  23. • But here comes a new problem. If we can’t

    create modules inside modules, there must be a place where those modules are instantiated. • Modules with huge constructors including lots of dependencies, code will become dirty and hard to read. • You can not to access the constructor of an activity
  24. Dependency Injector

  25. We can consider it as another module in our app

    that is in charge of providing instances of the rest of modules and inject their dependencies. • Dagger 2 (Dagger-android :S) • Koin • Kodein-DI • Service Locator
  26. Dagger 2

  27. Dagger 2

  28. • Standard JSR 330 • Compile-time validation of the entire

    graph • No Runtime Error • Performance & Easy debugging (I used to dagger 1) • Easy configuration of complex dependencies. • Scoped instances • Scalability • Simplifies access to shared instances • Supported by Google Why do I use Dagger 2 ?
  29. • Less Flexible • Steep Learning curve • Not standard

    configuration • Performance • Written in Java Smells Dagger 2 The community says:
  30. Understanding Dagger 2

  31. Annotations in Dagger 2 • @Module • @Provides • @Component

    • @Inject • @Qualifier(@Named) • @Scope (@Singleton) • @Bind • @BindInstance • more...
  32. Demo

  33. Top 10 ATP Rankings (work-in-progress ⛏ ) https://github.com/erikjhordan-rey/

  34. AppComponent Application PlayersComponent PlayersModule PlayersUiModule

  35. Provide Dependencies

  36. @Module class AppModule { var application: Application constructor(application: Application) {

    this.application = application } @Provides fun providesApplication(): Application { return application } }
  37. @Module class PlayersModule { @Provides @FeatureScope fun providePlayerLocalDataSource(application: Application) :

    PlayerLocalDataSource { return PlayerLocalDataSource(application.applicationContext) } @Provides @FeatureScope fun providePlayerRepository(playerLocalDataSource: PlayerLocalDataSource) : PlayerRepository { return PlayerRepository(playerLocalDataSource) } }
  38. Dagger + Kotlin ❤

  39. Inline method provide

  40. @Provides @FeatureScope fun providePlayerLocalDataSource(application: Application) : PlayerLocalDataSource { return PlayerLocalDataSource(application.applicationContext)

    } @Provides @FeatureScope fun providePlayerRepository(playerLocalDataSource: PlayerLocalDataSource) : PlayerLocalDataSource = PlayerRepository(playerLocalDataSource) @Provides @FeatureScope fun providePlayerRepository(playerLocalDataSource: PlayerLocalDataSource) = PlayerRepository(playerLocalDataSource)
  41. Static Provide Functions

  42. @Module object PlayersModule { @Provides @FeatureScope fun providePlayerLocalDataSource(application: Application) =

    PlayerLocalDataSource(application.applicationContext) @Provides @FeatureScope fun providePlayerRepository(playerLocalDataSource: PlayerLocalDataSource) = PlayerRepository(playerLocalDataSource) }
  43. AppComponent

  44. @Component(modules = [AppModule::class]) @Singleton interface AppComponent { fun provideApplication(): Application

    }
  45. @Module class AppModule { var application: Application constructor(application: Application) {

    this.application = application } @Provides fun providesApplication(): Application = application } @Component(modules = [AppModule::class]) @Singleton interface AppComponent { fun provideApplication(): Application }
  46. Annotations BindsInstace @BindsInstace

  47. @Component @Singleton interface AppComponent { @Component.Builder interface Builder { @BindsInstance

    fun application(application: Application): Builder fun build(): AppComponent } fun provideApplication(): Application }
  48. @Binds

  49. interface TennisReferee { fun name() } class Referee : TennisReferee

    { override fun name() { print("\n Referee Name") } } @Module abstrac class RefereesModule { @Binds abstract fun bindReferee(referee: Referee) : TennisReferee }
  50. Implicit Injection

  51. class GetPlayersUseCase @Inject constructor(private val playerRepository: PlayerRepository) { @WorkerThread suspend

    fun getAllPlayers() = playerRepository.getAllPlayers() }
  52. Setup DI Graph

  53. class App : Application() { lateinit var appComponent: AppComponent override

    fun onCreate() { super.onCreate() initInjector() } private fun initInjector() { appComponent = DaggerAppComponent.builder().appModule(AppModule(this)).build() } }
  54. class App : Application() { private val appComponent: AppComponent by

    lazy { DaggerAppComponent.builder().application(this).build() } companion object { @JvmStatic fun appComponent(context: Context) = (context.applicationContext as App).appComponent } fun Activity.appComponent() = App.appComponent(this) }
  55. ViewModel + Factory

  56. class PlayersViewModel constructor(private val getPlayersUseCase: GetPlayersUseCase) : ViewModel() { private

    val _playerListState = MutableLiveData<List<Player>>() val playerListState: LiveData<List<Player>> get() = _playerListState fun loadPlayers() { viewModelScope.launch { val result = async { getPlayersUseCase.getAllPlayers() } mainThread { _playerListState.value = result } } } }
  57. class PlayersViewModelFactory @Inject constructor(private val getPlayersUseCase: GetPlayersUseCase) : ViewModelProvider.Factory {

    @Suppress("UNCHECKED_CAST") override fun <T : ViewModel?> create(modelClass: Class<T>) = if (modelClass.isAssignableFrom(PlayersViewModel::class.java)) PlayersViewModel(getPlayersUseCase) as T else throw IllegalArgumentException("Unknown ViewModel class") }
  58. class PlayersUiModule(private val playersActivity: PlayersActivity) { @Provides fun providePlayersViewModelFactory(getPlayersUseCase: GetPlayersUseCase)

    = PlayersViewModelFactory(getPlayersUseCase) @Provides fun providePlayersViewModel(playersViewModelFactory: PlayersViewModelFactory): PlayersViewModel = playersActivity.viewModelProvidersOf(playersViewModelFactory) }
  59. class PlayersActivity : AppCompatActivity() { @Inject lateinit var viewModel: PlayersViewModel

    override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val appComponent = (application as App).appComponent DaggerPlayersComponent.builder() .playersUiModule(PlayersUiModule(this)) .appComponent(appComponent) .build() .inject(this) .... }
  60. fun inject(playersActivity: PlayersActivity) = playersActivity.run { DaggerPlayersComponent.builder() .playersUiModule(PlayersUiModule(this)) .appComponent(appComponent()) .build()

    .inject(this) } Injector.kt file
  61. class PlayersActivity : AppCompatActivity() { @Inject lateinit var viewModel: PlayersViewModel

    override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) inject(this) .... }
  62. Multibindings

  63. @Suppress("UNCHECKED_CAST") @FeatureScope class ViewModelFactory @Inject constructor( private val viewModelProviderMap: Map<Class<out

    ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>) : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { val viewModelProvider = viewModelProviderMap[modelClass] ?: throw IllegalArgumentException("Unknown ViewModel class: ${modelClass.name}") return viewModelProvider.get() as T } } ViewModelFactory
  64. @Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER) @MapKey annotation class ViewModelKey(val value: KClass<out ViewModel>)

    @Module abstract class ViewModelModule { @Binds @IntoMap @ViewModelKey(PlayersViewModel::class) abstract fun bindPlayersViewModel(playersViewModel: PlayersViewModel): ViewModel }
  65. class PlayersActivity : AppCompatActivity() { @Inject lateinit var viewModelFactory: ViewModelFactory

    lateinit var viewModel: PlayersViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) inject(this) viewModel = ViewModelProviders.of(this,viewModelFactory).get(PlayersViewModel::class.java) ... }) } }
  66. Dagger + Kotlin ❤

  67. inline fun <reified T : ViewModel> AppCompatActivity.viewModelProvidersOf(viewModelFactory: ViewModelProvider.Factory): T =

    ViewModelProviders.of(this, viewModelFactory)[T::class.java]
  68. class PlayersActivity : AppCompatActivity() { @Inject lateinit var viewModelFactory: ViewModelFactory

    lateinit var viewModel: PlayersViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) inject(this) viewModel = viewModelProvidersOf(viewModelFactory) ... } }
  69. Use the library in which your team feels more comfortable

    and fits your project!
  70. The usage of IoC, Dependency Inversion and Dependency Injection provides

    give us the following benefits: • Testability • Decoupling • Modularity (Flexible) • Maintainability
  71. Further Reading • Dependency Injection - Developers Android https://developer.android.com/training/dependency-injection •

    An Opinionated Guide to Dependency Injection on Android https://youtu.be/o-ins1nvbDg • Android Dependency Injection https://erikjhordan-rey.github.io/blog/2016/04/25/ANDROID- dependency-injection.html • Android Dependency Injection Guidance https://medium.com/androiddevelopers/dependency-injecti on-guidance-on-android-ads-2019-b0b56d774bc2
  72. Thank You DevFest CDMX 19! December 2019 erikjhordan.rey@gmail.com @ErikJhordan_Rey https://github.com/erikjhordan-rey/