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

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 )

Erik Jhordan Rey

December 14, 2019
Tweet

More Decks by Erik Jhordan Rey

Other Decks in Programming

Transcript

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

    code, usually because one of them uses the other to do something.
  2. class Player1 { fun serve() { print("\n Player 1 -

    Serve") } } class Player2 { fun drive() { print("\n Player 2 - Drive") } }
  3. 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
  4. 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.
  5. 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.
  6. 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
  7. 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
  8. 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") } }
  9. 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() }
  10. • 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.
  11. • 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
  12. 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
  13. • 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 ?
  14. • Less Flexible • Steep Learning curve • Not standard

    configuration • Performance • Written in Java Smells Dagger 2 The community says:
  15. Annotations in Dagger 2 • @Module • @Provides • @Component

    • @Inject • @Qualifier(@Named) • @Scope (@Singleton) • @Bind • @BindInstance • more...
  16. @Module class AppModule { var application: Application constructor(application: Application) {

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

    PlayerLocalDataSource { return PlayerLocalDataSource(application.applicationContext) } @Provides @FeatureScope fun providePlayerRepository(playerLocalDataSource: PlayerLocalDataSource) : PlayerRepository { return PlayerRepository(playerLocalDataSource) } }
  18. @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)
  19. @Module object PlayersModule { @Provides @FeatureScope fun providePlayerLocalDataSource(application: Application) =

    PlayerLocalDataSource(application.applicationContext) @Provides @FeatureScope fun providePlayerRepository(playerLocalDataSource: PlayerLocalDataSource) = PlayerRepository(playerLocalDataSource) }
  20. @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 }
  21. @Component @Singleton interface AppComponent { @Component.Builder interface Builder { @BindsInstance

    fun application(application: Application): Builder fun build(): AppComponent } fun provideApplication(): Application }
  22. 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 }
  23. class App : Application() { lateinit var appComponent: AppComponent override

    fun onCreate() { super.onCreate() initInjector() } private fun initInjector() { appComponent = DaggerAppComponent.builder().appModule(AppModule(this)).build() } }
  24. 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) }
  25. 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 } } } }
  26. 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") }
  27. class PlayersUiModule(private val playersActivity: PlayersActivity) { @Provides fun providePlayersViewModelFactory(getPlayersUseCase: GetPlayersUseCase)

    = PlayersViewModelFactory(getPlayersUseCase) @Provides fun providePlayersViewModel(playersViewModelFactory: PlayersViewModelFactory): PlayersViewModel = playersActivity.viewModelProvidersOf(playersViewModelFactory) }
  28. 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) .... }
  29. class PlayersActivity : AppCompatActivity() { @Inject lateinit var viewModel: PlayersViewModel

    override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) inject(this) .... }
  30. @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
  31. @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 }
  32. 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) ... }) } }
  33. 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) ... } }
  34. The usage of IoC, Dependency Inversion and Dependency Injection provides

    give us the following benefits: • Testability • Decoupling • Modularity (Flexible) • Maintainability
  35. 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