Share
Embed
Android Dagger Therapy Session
Android, Dagger, and Mangroves.
Slides from talk at GDG Boulder Jan 11, 2018.
Joshua Jamison
January 11, 2018
Transcript
65 slides
-
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 -
Josh likes this Separation of Concerns and Inversion of Control
stuff Josh decides to use Dagger Dagger -
Dagger Dependency Injection framework Separates object creation from business logic
Reusability Testability -
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" ... } -
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() } } -
Component? Component is one-half of an Object Graph. Defines graph
relationships Defines injectable targets -
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 -
Adding an injection target to your component @Component @Component interface
MangroveComponent { fun inject(target: MangroveApplication) } -
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) } } -
Update the component with a Module Adding Modules @Module class
MangroveModule @Component(modules = [MangroveModule::class]) interface MangroveComponent { fun inject(target: MangroveApplication) } -
Module? Modules serve as the other half of the object
graph Defines how dependencies are created Not necessary to be 1-1 with Component -
Providing dependencies from a Module Providing dependencies @Module class MangroveModule
{ @Provides fun provideApiKey(): String = "110b1a3cd59a299dc8a1f14" } -
Injecting into the target from the component Injecting class MangroveApplication
: Application() { … @Inject lateinit var cacheFileName: String override fun onCreate() { super.onCreate() component.inject(this) } } -
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: -
Qualifier? Uniqueness identifier for providing an object Usually used for
primitive and common types -
Providing multiple, separate dependencies of the same Type Qualifiers @Qualifier
@Retention(AnnotationRetention.RUNTIME) annotation class CacheFile @Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class ApiKey -
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" -
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) } } -
Including a DataModule with MangroveModule Adding more Modules @Module(includes =
[DataModule::class]) class MangroveModule -
Sometimes modules have dependencies of their own Module Dependencies @Module
class DataModule { @Provides fun provideSharedPreferences(context: Context): SharedPreferences { return PreferenceManager.getDefaultSharedPreferences(context) } } -
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 } } -
Update the Application to use the builder and supply the
dependency Module Dependencies private val component by lazy { DaggerMangroveComponent.builder() .appContext(applicationContext) .build() } -
Subcomponent? Inherit and extend the object graph of a parent
component. Useful for splitting object graph into smaller subgraphs. -
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 -
Module referenced subcomponents Subcomponents @Module(includes = [DataModule::class], subcomponents = [UserComponent::class])
class MangroveModule @Subcomponent interface UserComponent { @Subcomponent.Builder interface Builder { fun build(): UserComponent } } -
Module referenced subcomponents Subcomponents @Module class UserModule @Subcomponent(modules = [UserModule::class])
interface UserComponent { ... } -
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 -
@Component(modules = [MangroveModule::class]) interface MangroveComponent {A … fun userComponentBuilder(): UserComponent.Builder
… }A Using the subcomponent Subcomponents -
Using the subcomponent Subcomponents val app: MangroveApplication = (application as
MangroveApplication) app.component .userComponentBuilder() .build() .inject(this) -
Scope? Without Scope a new instance is created for each
injection Scopes persist injected object Inject same instance for lifetime of scope -
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 {} -
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) } } -
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 -
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 } -
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 } } -
MVP implementation example Scope interface Contract { abstract class Presenter(val
view: View) { ... } interface View { ... } } class MainPresenter(view: MainContract.View): MainContract.Presenter(view) { ... } -
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 } -
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) } } -
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 -
Install AndroidInjectionModule in the Application Component Dagger Android @Singleton @Component(modules
= [ AndroidSupportInjectionModule::class, MangroveModule::class ]) interface MangroveComponent { ... } -
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 } } -
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>() } -
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> } -
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 -
Simplify Activity injection Dagger Android class MainActivity: Activity() { …
override fun onCreate(Bundle savedInstanceState) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) } } -
Subcomponent Builder parameters (MVP)? Activity already provided, just inject where
needed. Dagger Android @Binds abstract fun view(activity: MainActivity): MainContract.View -
Subcomponent Builder parameters (MVP)? Others can be provided using Activity
members Dagger Android @Provides fun provideIntent(activity: MainActivity): Intent { return activity.intent } -
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 -
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. -
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. -
Add dependencies Architecture Components // Android Architecture Components implementation "android.arch.lifecycle:extensions:1.0.0"
annotationProcessor "android.arch.lifecycle:compiler:1.0.0" -
New ViewModel classes useful with MVVM pattern Architecture Components @Per(UserComponent::class)
class UserViewModel @Inject constructor(val userModel: UserModel) : ViewModel() { fun loadUserData(): Observable<UserData> { … } } -
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 { ... } } } -
Probably better to inject a custom ViewModelProvider.Factory Architecture Components @Singleton
class MangroveViewModelFactory: ViewModelProvider.Factory { private val creators: Map<Class<out ViewModel>, Provider<ViewModel>> … } -
Review Joshua is a great artist. Dagger is neat. Review
slides are pointless great. Mangroves are pretty cool.