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

Dependency Injection with Dagger 2

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers. →

Dependency Injection with Dagger 2

Although Dagger 2 is one of the best dependency injection frameworks on Android, big steep learning curves and a ton of ways to utilize really make a hard time for beginners. This talk covers core concepts of dagger and dependency injection in general, what to do and what should avoid.

Avatar for nlgtuankiet

nlgtuankiet

May 23, 2019

More Decks by nlgtuankiet

Other Decks in Programming

Transcript

  1. fun main() { val repository = repository.sayHelloWorld() } val database

    = Database() val restApi = RestApi() Dependency Injection? class Database class RestApi class Repository fun sayHelloWorld() { println("Hello world!") } } Repository(database, restApi) (val database: Database, val restApi: RestApi) {
  2. fun main() { val repository = repository.sayHelloWorld() } Dependency Injection?

    class Database class RestApi class Repository fun sayHelloWorld() { println("Hello world!") } } (val database: Database, val restApi: RestApi) {
  3. fun main() { val repository = repository.sayHelloWorld() } Dependency Injection?

    class Database class RestApi class Repository fun sayHelloWorld() { println("Hello world!") } } @Inject constructor() @Inject constructor() @Inject constructor() (val database: Database, val restApi: RestApi) {
  4. @Component interface MyComponent { fun provideRepo(): Repository } fun main()

    { val repository = repository.sayHelloWorld() } Dependency Injection? class Database class RestApi class Repository fun sayHelloWorld() { println("Hello world!") } } @Inject constructor() @Inject constructor() @Inject constructor() (val database: Database, val restApi: RestApi) {
  5. @Component interface MyComponent { fun provideRepo(): Repository } fun main()

    { val repository = repository.sayHelloWorld() } Dependency Injection? class Database class RestApi class Repository fun sayHelloWorld() { println("Hello world!") } } @Inject constructor() @Inject constructor() @Inject constructor() DaggerMyComponent.create().provideRepo() (val database: Database, val restApi: RestApi) {
  6. @Inject "// javax.inject.Inject class RestApi @Inject constructor() class Repository @Inject

    constructor( val database: Database, val restApi: RestApi ) { fun sayHelloWorld() { println("Hello world!") } } When annotate in type’s constructor • Describe type’s dependencies • Request those dependences • Put type into the graph
  7. Component @Component interface MyComponent { fun provideRepo(): Repository } •

    Can be interface or abstract class • The graph much be fulfilled • Can declare provision methods • Dagger will generate concrete implementation, prefix its name with Dagger • Example: MyComponent -> DaggerMyComponent
  8. Component @Component interface MyComponent { fun provideRepo(): Repository } •

    Can be interface or abstract class • The graph much be fulfilled • Can declare provision methods • Dagger will generate concrete implementation, prefix its name with Dagger • Example: MyComponent -> DaggerMyComponent Database RestApi Repository
  9. Component @Component interface MyComponent { fun provideRepo(): Repository } •

    Can be interface or abstract class • The graph much be fulfilled • Can declare provision methods • Dagger will generate concrete implementation, prefix its name with Dagger • Example: MyComponent -> DaggerMyComponent RestApi Repository
  10. Component @Component interface MyComponent { fun provideRepo(): Repository } •

    Can be interface or abstract class • The graph much be fulfilled • Can declare provision methods • Dagger will generate concrete implementation, prefix its name with Dagger • Example: MyComponent -> DaggerMyComponent RestApi Repository
  11. Example class Database @Inject constructor() class RestApi @Inject constructor() class

    Repository @Inject constructor(val database: Database, val restApi: RestApi) { fun sayHelloWorld() { println("Hello world!") } } @Component interface MyComponent { fun provideRepo(): Repository } fun main() { val repository = DaggerMyComponent.create().provideRepo() repository.sayHelloWorld() }
  12. Materials • @Component • Entry point to all of your

    dependencies • @Inject • Declare type dependencies • Add type to the dependency graph • @Module • Add type to the dependency graph which are: • Library type that can’t be annotated with @Inject • Configurable object • Interface or abstract class (usually for binding interface to its implementation)
  13. Field Injection class Repository { lateinit var database: Database lateinit

    var restApi: RestApi } Field Injection Application Activity BroadcastReceiver Service ContentProvider Prefer Constructor Injection Fragment View
  14. Field Injection class Repository { lateinit var database: Database lateinit

    var restApi: RestApi } WARNING Use this only when you have no control over how this class is created Field Injection Application Activity BroadcastReceiver Service ContentProvider Prefer Constructor Injection Fragment View
  15. Field Injection @Component interface MyComponent { fun inject(repo: Repository) }

    class Repository { @Inject lateinit var database: Database @Inject lateinit var restApi: RestApi init { DaggerMyComponent.create().inject(this) "// internal dagger will do: "// repository.database "== ""... "// repository.restApi "== ""... "// that why these field MUCH BE PUBLIC } }
  16. Component • Can declare: • Provision method (provide a dependency)

    • Injection method (inject dependencies into an instance) • These methods: • Can have any name • But method signature must match
  17. { return impl } Binding interface Repository class RepositoryImpl @Inject

    constructor( ): Repository fun repository(impl: RepositoryImpl): Repository @Provides NOTE: a module can only have ether @Binds or @Provides
  18. Binding interface Repository class RepositoryImpl @Inject constructor( ): Repository fun

    repository(impl: RepositoryImpl): Repository @Binds NOTE: a module can only have ether @Binds or @Provides
  19. Dependency Request class Foo @Inject constructor() { init { "//

    super heavy object Thread.sleep(10_000) } } class Bar @Inject constructor( val foo: Foo, val fooProvider: Provider<Foo> )
  20. Multi-binding @Module interface Binding { @Binds @IntoMap @ClassKey(TaskDetailViewModel"::class) fun taskDetail(vm:

    TaskDetailViewModel): ViewModel @Binds @IntoMap @ClassKey(AddEditViewModel"::class) fun addEdit(vm: AddEditViewModel): ViewModel }
  21. Multi-binding class TodoViewModelFactory @Inject constructor( @JvmSuppressWildcards val viewModelProviders: Map<Class<out ViewModel>,

    Provider<ViewModel">> ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun <T : ViewModel?> create(modelClass: Class<T>): T { return viewModelProviders[modelClass]"?.get() as? T "?: throw IllegalArgumentException("Unknown ViewModel class") } }
  22. ClassKey /** * A {@link MapKey} annotation for maps with

    {@code Class<?>} keys. * * <p>If your map's keys can be constrained, consider using a custom * annotation instead, with a * member whose type is {@code Class<? extends Something>}. "*/ @Documented @Target(METHOD) @Retention(RUNTIME) @MapKey public @interface ClassKey { Class<?> value(); }
  23. But why? • To provide dependencies that are only available

    at component creation time • Example: Context, Activity • To provide module that can’t be instantiated by Dagger
  24. @Module class GsonModule(val debugable: Boolean) { @Singleton @Provides fun provideGson():

    Gson { val builder = GsonBuilder() if (debugable) { builder.setPrettyPrinting() } return builder.create() } } class RestApi(val token: String) @Singleton @Component(modules = [GsonModule"::class]) interface MyComponent { @Component.Factory interface Factory { fun create( @BindsInstance restApi: RestApi, gsonModule: GsonModule ): MyComponent } }
  25. fun main() { "// val component = DaggerMyComponent.create() val token

    = "bla bla" val restApi = RestApi(token) val component = DaggerMyComponent.factory().create( restApi = restApi, gsonModule = GsonModule(debugable = false) ) }
  26. Singleton @Module object GsonModule { @Singleton @JvmStatic @Provides fun provideGson():

    Gson { "// bla bla } } @Singleton class RestApi @Inject constructor() @Singleton @Component(modules = [GsonModule"::class]) interface MyComponent
  27. Singleton • A component is not singleton • Dependency is

    singleton within a component only • Component must have same scope with its dependencies
  28. Dagger Scope /** * Identifies a type that the injector

    only instantiates once. * Not inherited. * * @see javax.inject.Scope @Scope "*/ @Scope @Documented @Retention(RUNTIME) public @interface Singleton {}
  29. @Subcomponent • Use to extend the graph from a component

    • Is a component • Must have a Factory • Must have different scope from parent component’s • Inherit the dependency graph from parent component
  30. App Activity @Subcomponent @ActivityScope interface MainActivitySubcomponent { @Subcomponent.Factory interface Factory

    { fun create( @BindsInstance activity: MainActivity ): MainActivitySubcomponent } } @Module(subcomponents = [MainActivitySubcomponent"::class]) interface MainActivityModule @AppScope @Component(modules = [MainActivityModule"::class]) interface AppComponent { fun activityComponentFactory(): MainActivitySubcomponent.Factory }
  31. class MyApplication : Application() { lateinit var appComponent: AppComponent override

    fun onCreate() { appComponent = DaggerAppComponent.create() "// bla bla } } class MainActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { (applicationContext as MyApplication) .appComponent .activityComponentFactory() .create(this) .inject(this) super.onCreate(savedInstanceState) } }
  32. @Subcomponent @ActivityScope interface MainActivitySubcomponent { @Subcomponent.Factory interface Factory { fun

    create( @BindsInstance activity: MainActivity ): MainActivitySubcomponent } } @Module(subcomponents = [MainActivitySubcomponent"::class]) interface MainActivityModule @AppScope @Component(modules = [MainActivityModule"::class]) interface AppComponent { fun activityComponentFactory(): MainActivitySubcomponent.Factory }
  33. @Module interface MainActivityModule { @ActivityScope @ContributesAndroidInjector fun contributeActivity(): MainActivity }

    @AppScope @Component( modules = [ AndroidSupportInjectionModule"::class, MainActivityModule"::class ] ) interface AppComponent
  34. class TodoApplication : DaggerApplication() { lateinit var appComponent: AppComponent override

    fun applicationInjector(): AndroidInjector<out DaggerApplication> { return appComponent } override fun onCreate() { appComponent = DaggerAppComponent.create() super.onCreate() } } class MainActivity : DaggerActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } }