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

Android Dagger Therapy Session

Android Dagger Therapy Session

Android, Dagger, and Mangroves.

Slides from talk at GDG Boulder Jan 11, 2018.

Joshua Jamison

January 11, 2018
Tweet

More Decks by Joshua Jamison

Other Decks in Technology

Transcript

  1. 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
  2. Josh likes this Separation of Concerns and Inversion of Control

    stuff Josh decides to use Dagger Dagger
  3. 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" ... }
  4. 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() } }
  5. 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
  6. Adding an injection target to your component @Component @Component interface

    MangroveComponent { fun inject(target: MangroveApplication) }
  7. 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) } }
  8. Update the component with a Module Adding Modules @Module class

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

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

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

    : Application() { … @Inject lateinit var cacheFileName: String override fun onCreate() { super.onCreate() component.inject(this) } }
  12. 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:
  13. Providing multiple, separate dependencies of the same Type Qualifiers @Qualifier

    @Retention(AnnotationRetention.RUNTIME) annotation class CacheFile @Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class ApiKey
  14. 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"
  15. 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) } }
  16. Sometimes modules have dependencies of their own Module Dependencies @Module

    class DataModule { @Provides fun provideSharedPreferences(context: Context): SharedPreferences { return PreferenceManager.getDefaultSharedPreferences(context) } }
  17. 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 } }
  18. Update the Application to use the builder and supply the

    dependency Module Dependencies private val component by lazy { DaggerMangroveComponent.builder() .appContext(applicationContext) .build() }
  19. Subcomponent? Inherit and extend the object graph of a parent

    component. Useful for splitting object graph into smaller subgraphs.
  20. 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
  21. Module referenced subcomponents Subcomponents @Module(includes = [DataModule::class], subcomponents = [UserComponent::class])

    class MangroveModule @Subcomponent interface UserComponent { @Subcomponent.Builder interface Builder { fun build(): UserComponent } }
  22. 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
  23. Using the subcomponent Subcomponents val app: MangroveApplication = (application as

    MangroveApplication) app.component .userComponentBuilder() .build() .inject(this)
  24. Scope? Without Scope a new instance is created for each

    injection Scopes persist injected object Inject same instance for lifetime of scope
  25. 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 {}
  26. 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) } }
  27. 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
  28. 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 }
  29. 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 } }
  30. MVP implementation example Scope interface Contract { abstract class Presenter(val

    view: View) { ... } interface View { ... } } class MainPresenter(view: MainContract.View): MainContract.Presenter(view) { ... }
  31. 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 }
  32. 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) } }
  33. 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
  34. Install AndroidInjectionModule in the Application Component Dagger Android @Singleton @Component(modules

    = [ AndroidSupportInjectionModule::class, MangroveModule::class ]) interface MangroveComponent { ... }
  35. 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 } }
  36. 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>() }
  37. 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> }
  38. 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
  39. Simplify Activity injection Dagger Android class MainActivity: Activity() { …

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

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

    members Dagger Android @Provides fun provideIntent(activity: MainActivity): Intent { return activity.intent }
  42. 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
  43. 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.
  44. 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.
  45. New ViewModel classes useful with MVVM pattern Architecture Components @Per(UserComponent::class)

    class UserViewModel @Inject constructor(val userModel: UserModel) : ViewModel() { fun loadUserData(): Observable<UserData> { … } }
  46. 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 { ... } } }
  47. Probably better to inject a custom ViewModelProvider.Factory Architecture Components @Singleton

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

    slides are pointless great. Mangroves are pretty cool.