Android Dagger Therapy Session

Android Dagger Therapy Session

Android, Dagger, and Mangroves.

Slides from talk at GDG Boulder Jan 11, 2018.

5e8d8ef1b67dd20143230051a77b2e47?s=128

Joshua Jamison

January 11, 2018
Tweet

Transcript

  1. Android Dagger Therapy Session Joshua Jamison

  2. Meet Josh

  3. Story Time Josh wrote an app.

  4. Well… better at least. More organized, less spaghetti. But this

    time he’s going to do it “right”… Josh decides to write a new app from scratch. Story Time Mangrove
  5. Josh likes this Separation of Concerns and Inversion of Control

    stuff Josh decides to use Dagger Dagger
  6. Dagger Dependency Injection framework Separates object creation from business logic

    Reusability Testability
  7. Josh starts a new application. New App Stuff

  8. Updating build.gradle Time for Dagger apply plugin: ‘kotlin-kapt' … dependencies

    { ... // Dagger implementation "com.google.dagger:dagger:2.13" kapt "com.google.dagger:dagger-compiler:2.13" ... }
  9. Defines a root “Application” Component and stores an instance of

    it on the Application @Component @Component interface MangroveComponent class MangroveApplication : Application() { val component: MangroveComponent by lazy { DaggerMangroveComponent.create() } }
  10. Component? Component is one-half of an Object Graph. Defines graph

    relationships Defines injectable targets
  11. Serve as the “root node” for the object graph. Application

    Component Root Node Child A Child A-A Child B Child C Child A-A-A Child A-A-B
  12. Adding an injection target to your component @Component @Component interface

    MangroveComponent { fun inject(target: MangroveApplication) }
  13. Also be sure to call inject @Component class MangroveApplication :

    Application() { val component: MangroveComponent by lazy { DaggerMangroveComponent.create() } override fun onCreate() { super.onCreate() component.inject(this) } }
  14. Update the component with a Module Adding Modules @Module class

    MangroveModule @Component(modules = [MangroveModule::class]) interface MangroveComponent { fun inject(target: MangroveApplication) }
  15. Module? Modules serve as the other half of the object

    graph Defines how dependencies are created Not necessary to be 1-1 with Component
  16. Providing dependencies from a Module Providing dependencies @Module class MangroveModule

    { @Provides fun provideApiKey(): String = "110b1a3cd59a299dc8a1f14" }
  17. Injecting into the target from the component Injecting class MangroveApplication

    : Application() { … @Inject lateinit var cacheFileName: String override fun onCreate() { super.onCreate() component.inject(this) } }
  18. Sometimes you need to provide two different objects of the

    same type Qualifiers @Provides fun provideApiKey(): String = "110b1a3cd59a299dc8a1f14" @Provides fun provideCacheFileName(): String = "mangrove-cache" e: /Users/joshuajamison/Projects/personal/Mangrove/app/build/tmp/kapt3/ stubs/debug/com/codeundone/mangrove/MangroveComponent.java:7: error: java.lang.String is bound multiple times:
  19. Qualifier? Uniqueness identifier for providing an object Usually used for

    primitive and common types
  20. Providing multiple, separate dependencies of the same Type Qualifiers @Qualifier

    @Retention(AnnotationRetention.RUNTIME) annotation class CacheFile @Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class ApiKey
  21. Sometimes you need to provide two different objects of the

    same type Qualifiers @Provides @ApiKey fun provideApiKey(): String = "110b1a3cd59a299dc8a1f14" @Provides @CacheFile fun provideCacheFileName(): String = "mangrove-cache"
  22. Providing multiple, separate dependencies of the same Type Qualifiers class

    MangroveApplication : Application() { … @Inject @CacheFile lateinit var cacheFileName: String override fun onCreate() { super.onCreate() component.inject(this) } }
  23. Including a DataModule with MangroveModule Adding more Modules @Module(includes =

    [DataModule::class]) class MangroveModule
  24. Sometimes modules have dependencies of their own Module Dependencies @Module

    class DataModule { @Provides fun provideSharedPreferences(context: Context): SharedPreferences { return PreferenceManager.getDefaultSharedPreferences(context) } }
  25. Use @BindsInstance to provide Application instance in @Component.Builder Module Dependencies

    @Component(modules = [DataModule::class]) interface MangroveComponent { … @Component.Builder interface Builder { @BindsInstance fun appContext(context: Context): Builder fun build(): MangroveComponent } }
  26. Update the Application to use the builder and supply the

    dependency Module Dependencies private val component by lazy { DaggerMangroveComponent.builder() .appContext(applicationContext) .build() }
  27. Mangrove Break

  28. Extending the object graph with Subcomponents Subcomponents @Subcomponent interface UserComponent

  29. Subcomponent? Inherit and extend the object graph of a parent

    component. Useful for splitting object graph into smaller subgraphs.
  30. Multiple ways to connect a Subcomponent to a parent Component.

    Can be done from a module included by the parent Component, or by the Subcomponent’s dependencies parameter. Module defined subcomponents are general recommended. Subcomponents
  31. Module referenced subcomponents Subcomponents @Module(includes = [DataModule::class], subcomponents = [UserComponent::class])

    class MangroveModule @Subcomponent interface UserComponent { @Subcomponent.Builder interface Builder { fun build(): UserComponent } }
  32. Module referenced subcomponents Subcomponents @Module class UserModule @Subcomponent(modules = [UserModule::class])

    interface UserComponent { ... }
  33. Using the subcomponent Subcomponents @Component(modules = [MangroveModule::class]) interface MangroveComponent {A

    fun inject(target: MangroveApplication) fun userComponentBuilder(): UserComponent.Builder @Component.Builder interface Builder { @BindsInstance fun appContext(context: Context): Builder fun build(): MangroveComponent } }A
  34. @Component(modules = [MangroveModule::class]) interface MangroveComponent {A … fun userComponentBuilder(): UserComponent.Builder

    … }A Using the subcomponent Subcomponents
  35. Using the subcomponent Subcomponents val app: MangroveApplication = (application as

    MangroveApplication) app.component .userComponentBuilder() .build() .inject(this)
  36. Reusing injection objects with scopes Scope

  37. Scope? Without Scope a new instance is created for each

    injection Scopes persist injected object Inject same instance for lifetime of scope
  38. Singleton Scope Scope /** * Identifies a type that the

    injector only instantiates once. Not inherited. * * @see javax.inject.Scope @Scope */ @Scope @Documented @Retention(RUNTIME) public @interface Singleton {}
  39. Scope Using a scope @Singleton @Component(modules = [MangroveModule::class]) interface MangroveComponent

    … @Module class DataModule { @Provides @Singleton fun provideSharedPreferences(context: Context): SharedPreferences { return PreferenceManager.getDefaultSharedPreferences(context) } }
  40. Scopes can be helpful, but be careful Scoped injections are

    thread safe, but this means more overhead in the generated code Slower than a scope-less injection Use scopes for providing stateful objects Scope
  41. Making a Custom Scope Scope @Scope @Retention(RUNTIME) annotation class User

  42. Custom Scope with arguments Scope @Scope @Retention(AnnotationRetention.RUNTIME) annotation class Per(val

    value: KClass<*>) @Module class UserModule { @Provides @Per(UserComponent::class) fun provideUserModel(userManager: UserManager): UserModel = return userManager.currentUserModel }
  43. Activity specific scopes Scope @Per(MainComponent::class) @Subcomponent(modules = [MainModule::class]) interface MainComponent

    { fun inject(target: MainActivity) @Subcomponent.Builder interface Builder { @BindsInstance fun activity(activity: MainActivity): Builder fun build(): MainComponent } }
  44. MVP implementation example Scope interface Contract { abstract class Presenter(val

    view: View) { ... } interface View { ... } } class MainPresenter(view: MainContract.View): MainContract.Presenter(view) { ... }
  45. MVP implementation example Scope @Module abstract class MainModule { @Binds

    @Per(MainComponent::class) abstract fun provideView( activity: MainActivity): MainContract.View @Binds @Per(MainComponent::class) abstract fun providePresenter( presenter: MainPresenter): MainContract.Presenter }
  46. MVP implementation example Scope class MainActivity : AppCompatActivity(), MainContract.View {

    @Inject lateinit var presenter: MainContract.Presenter override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val app: MangroveApplication = (application as MangroveApplication) app.component.mainComponentBuilder() .activity(this) .build() .inject(this) } }
  47. Dagger & Android // Dagger … implementation "com.google.dagger:dagger-android:2.13" implementation “com.google.dagger:dagger-android-support:2.13"

    … Alternate way using Dagger’s new Android specific features First update build.gradle with dagger’s Android dependencies
  48. Install AndroidInjectionModule in the Application Component Dagger Android @Singleton @Component(modules

    = [ AndroidSupportInjectionModule::class, MangroveModule::class ]) interface MangroveComponent { ... }
  49. Update Application to implement HasActivityInjector and inject a DispatchingAndroidInjector Dagger

    Android class MangroveApp : Application(), HasActivityInjector { ... @Inject lateinit var activityInjector: DispatchingAndroidInjector<Activity> ... override fun activityInjector(): AndroidInjector<Activity> { return activityInjector } }
  50. Update activity Subcomponents and Builders to extend AndroidInjector Dagger Android

    @Subcomponent(modules = [LauncherModule::class]) @Per(LauncherComponent::class) interface LauncherComponent : AndroidInjector<LauncherActivity> { @Subcomponent.Builder abstract class Builder : AndroidInjector.Builder<LauncherActivity>() }
  51. Activity Module multibinding into ActivityKey map Dagger Android @Module(subcomponents =

    [MainComponent::class]) abstract class MangroveModule { … @Binds @IntoMap @ActivityKey(MainActivity::class) abstract fun bindLauncher(builder: MainComponent.Builder): AndroidInjector.Factory<out Activity> }
  52. Multibinding? Provide multiple objects of type T to a single

    Set<T> or Map<T> @IntoMap binding requires a Key argument The Map/Set is then injected instead of individual objects
  53. Simplify Activity injection Dagger Android class MainActivity: Activity() { …

    override fun onCreate(Bundle savedInstanceState) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) } }
  54. Subcomponent Builder parameters (MVP)? Activity already provided, just inject where

    needed. Dagger Android @Binds abstract fun view(activity: MainActivity): MainContract.View
  55. Subcomponent Builder parameters (MVP)? Others can be provided using Activity

    members Dagger Android @Provides fun provideIntent(activity: MainActivity): Intent { return activity.intent }
  56. Lifecycle

  57. Let’s review how a scope works. Scoped objects bound to

    lifecycle of scoped component. Activity scoped components stored on an Activity inherit same lifecycle. Activity lifecycle → destroyed and recreated from system events: eg. rotation, phone call. Lifecycle Concerns
  58. An Activity Scope isn’t persisted thru standard lifecycle events. Lifecycle

    Concerns Tread lightly. Don’t leak Context. Can lead to ANR/Crashes. Don’t use previous MVP implementation with this unless you drop the scope.
  59. Android Architecture Components → solution to lifecycle issues with new

    Lifecycle aware components. Architecture Components LifecycleObserver → Handles disconnecting from a LifecycleOwner when the lifecycle is ending. LifecycleOwner → Free with Support activities and fragments.
  60. Add dependencies Architecture Components // Android Architecture Components implementation "android.arch.lifecycle:extensions:1.0.0"

    annotationProcessor "android.arch.lifecycle:compiler:1.0.0"
  61. New ViewModel classes useful with MVVM pattern Architecture Components @Per(UserComponent::class)

    class UserViewModel @Inject constructor(val userModel: UserModel) : ViewModel() { fun loadUserData(): Observable<UserData> { … } }
  62. Inject ViewModel Provider in Activity class MainActivity: Activity { @Inject

    lateinit var viewModelFactory: ViewModelProvider.Factory lateinit var userViewModel: UserViewModel override fun onCreate(Bundle savedInstanceState) { … userViewModel = ViewModelProviders.of(this, viewModelFactory) .get(UserViewModel::class) userViewModel.loadUserData().subscribe { ... } } }
  63. Probably better to inject a custom ViewModelProvider.Factory Architecture Components @Singleton

    class MangroveViewModelFactory: ViewModelProvider.Factory { private val creators: Map<Class<out ViewModel>, Provider<ViewModel>> … }
  64. Review Joshua is a great artist. Dagger is neat. Review

    slides are pointless great. Mangroves are pretty cool.
  65. Links https://github.com/google/dagger https://developer.android.com/topic/ libraries/architecture/index.html https://github.com/googlesamples/ android-architecture-components Mangrove https://twitter.com/cephus +JoshuaJamison Joshua

    links