Slide 1

Slide 1 text

Working with Dagger and Kotlin Droidcon Greece ! @askashdavies

Slide 2

Slide 2 text

Introduction @askashdavies

Slide 3

Slide 3 text

What is Dagger @askashdavies

Slide 4

Slide 4 text

In Java @askashdavies

Slide 5

Slide 5 text

In Java, for Java @askashdavies

Slide 6

Slide 6 text

In Java, for Java, by Java @askashdavies

Slide 7

Slide 7 text

In Java, for Java, by Java, with Java developers @askashdavies

Slide 8

Slide 8 text

In Java, for Java, by Java, with Java developers, for Ja.... @askashdavies

Slide 9

Slide 9 text

Java @askashdavies

Slide 10

Slide 10 text

@askashdavies

Slide 11

Slide 11 text

Code Generation @askashdavies

Slide 12

Slide 12 text

AutoValue @askashdavies

Slide 13

Slide 13 text

Lombok @askashdavies

Slide 14

Slide 14 text

Dagger @askashdavies

Slide 15

Slide 15 text

jakewharton.com/helping-dagger-help-you/

Slide 16

Slide 16 text

History @askashdavies

Slide 17

Slide 17 text

Guice (Pronounced 'juice') @askashdavies

Slide 18

Slide 18 text

Dagger (JSR-330) @askashdavies

Slide 19

Slide 19 text

Dagger 2 (Sans-Reflection) youtube.com/watch?v=oK_XtfXPkqw

Slide 20

Slide 20 text

Kotlin (noun: freakin' awesome) @askashdavies

Slide 21

Slide 21 text

Dagger 2 Kotlin — @askashdavies

Slide 22

Slide 22 text

Dagger Qualifiers Qualifiers used to identify dependencies with identical signatures » Factories use qualifiers to decide the instance use » Can create your own qualifier annotations, or just use @Named. » Apply qualifiers by annotating the field or parameter of interest. » The type and qualifier annotation will both be used to identify the dependency. @askashdavies

Slide 23

Slide 23 text

Retention Annotation Use Kotlin retention annotations instead of Java retention » Java retention support is depreceted in Kotlin » At least BINARY retention but RUNTIME is ideal » Dagger 2 doesn’t operate on source files » Annotations are necessary for kapt @askashdavies

Slide 24

Slide 24 text

Constructor injection class Game @Inject constructor( @Named("P1") private val player1: Player, @Named("P2") private val player2: Player ) @askashdavies

Slide 25

Slide 25 text

Constructor injection public final class Game { private final Player player1; private final Player player2; @Inject public Game( @Named("P1") Player player1, @Named("P2") Player player2) { super(); this.player1 = player1; this.player2 = player2; } } @askashdavies

Slide 26

Slide 26 text

Constructor Injection public final class Game { private final Player player1; private final Player player2; @Inject public Game( @Named("P1") Player player1, @Named("P2") Player player2) { super(); this.player1 = player1; this.player2 = player2; } } @askashdavies

Slide 27

Slide 27 text

Field Injection: lateinit var class Game @Inject constructor() { @Inject @Named("P1") lateinit var player1: Player @Inject @Named("P2") lateinit var player2: Player } @askashdavies

Slide 28

Slide 28 text

Decompiled lateinit var public final class Game { @Inject public Player player1; @Inject public Player player2; @Named("P1") public static void player1$annotations() {} public final Player getPlayer1() { ... } public final void setPlayer1(Player var1) {...} @Named("P2") public static void player2$annotations() {} public final Player getPlayer2() { ... } public final void setPlayer2(Player var1) {...} @askashdavies

Slide 29

Slide 29 text

Decompiled lateinit var public final class Game { @Inject public Player player1; @Inject public Player player2; @Named("P1") public static void player1$annotations() {} public final Player getPlayer1() { ... } public final void setPlayer1(Player var1) {...} @Named("P2") public static void player2$annotations() {} public final Player getPlayer2() { ... } public final void setPlayer2(Player var1) {...} @askashdavies

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

Specify Annotations » @field:... » @set:... » @get:... » @param:... » @property:... » @setparam:... » @receiver:... » @delegete:... @askashdavies

Slide 32

Slide 32 text

@askashdavies

Slide 33

Slide 33 text

Specify Annotations class Game @Inject constructor() { @Inject @field:Named("P1") lateinit var player1: Player @Inject @field:Named("P2") lateinit var player2: Player } @askashdavies

Slide 34

Slide 34 text

Specify Annotations public final class Game { @Inject @Named("P1") public Player player1; @Inject @Named("P2") public Player player2; public final Player getPlayer1() {...} public final void setPlayer1(Player var1) {...} public final Player getPlayer2() {...} public final void setPlayer2(Player var1) {...} } @askashdavies

Slide 35

Slide 35 text

No content

Slide 36

Slide 36 text

Constructor vs Property injection » Constructor val » Easier to use » Reliable dependency injection » Compilation safety » Property lateinit var injection » Synthetic property accessors » Unclear where the annotation is applied » Dont forget to use with @field: @askashdavies

Slide 37

Slide 37 text

Scope Annotations @askashdavies

Slide 38

Slide 38 text

@Scope ! @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Scope { } @askashdavies

Slide 39

Slide 39 text

@Singleton @askashdavies

Slide 40

Slide 40 text

@Singleton != Singleton Pattern @askashdavies

Slide 41

Slide 41 text

@Singleton != Singleton Pattern public final class Singleton { private static final Singleton INSTANCE = new Singleton(); private Singleton() { } public static Singleton getInstance() { return INSTANCE; } } @askashdavies

Slide 42

Slide 42 text

@Singleton != Singleton Pattern object Singleton @askashdavies

Slide 43

Slide 43 text

@Scope @Scope @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class ActivityScope @askashdavies

Slide 44

Slide 44 text

@Scope @Module internal object ApplicationModule { @Provides @JvmStatic @ActivityScope fun context(application: Application): Context = application } @askashdavies

Slide 45

Slide 45 text

@ActivityScope @askashdavies

Slide 46

Slide 46 text

@Scope ! @ActivityScope // Don't do this! class ActivityRepository @Inject constructor() { } @askashdavies

Slide 47

Slide 47 text

@Reusable @askashdavies

Slide 48

Slide 48 text

Double Check public final class DoubleCheck implements Provider, Lazy { private static final Object UNINITIALIZED = new Object(); private volatile Provider provider; private volatile Object instance = UNINITIALIZED; private DoubleCheck(Provider provider) { /* ... */ } @Override public T get() { Object result = instance; if (result == UNINITIALIZED) { synchronized (this) { result = instance; if (result == UNINITIALIZED) { result = provider.get(); instance = reentrantCheck(instance, result); provider = null; } } } return (T) result; } public static Object reentrantCheck(Object currentInstance, Object newInstance) { /* ... */ } } @askashdavies

Slide 49

Slide 49 text

Double Check public final class DoubleCheck implements Provider, Lazy { private static final Object UNINITIALIZED = new Object(); private volatile Provider provider; private volatile Object instance = UNINITIALIZED; private DoubleCheck(Provider provider) { /* ... */ } @Override public T get() { Object result = instance; if (result == UNINITIALIZED) { synchronized (this) { result = instance; if (result == UNINITIALIZED) { result = provider.get(); instance = reentrantCheck(instance, result); provider = null; } } } return (T) result; } public static Object reentrantCheck(Object currentInstance, Object newInstance) { /* ... */ } } @askashdavies

Slide 50

Slide 50 text

Single Check public final class SingleCheck implements Provider { private static final Object UNINITIALIZED = new Object(); private volatile Provider provider; private volatile Object instance = UNINITIALIZED; private SingleCheck(Provider provider) { /* ... */ } @Override public T get() { Object local = instance; if (local == UNINITIALIZED) { Provider providerReference = provider; if (providerReference == null) { local = instance; } else { local = providerReference.get(); instance = local; provider = null; } } return (T) local; } } @askashdavies

Slide 51

Slide 51 text

Kotlin: Lazy private val viewModel by lazy(NONE) { SampleViewModel() } fun lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy = when (mode) { LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer) LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer) LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer) } @askashdavies

Slide 52

Slide 52 text

Favour @Reusable over @Scope » Great for expensive dependencies » Work great in single thread environments » Not guaranteed same instance in multiple threads » Prefer to keep your Dagger graph stateless » Use @Scope if you absolutely need to store state @askashdavies

Slide 53

Slide 53 text

Dagger: Modules @askashdavies

Slide 54

Slide 54 text

Status Quo ! @Module public abstract class ApplicationModule { @Binds abstract Context context(Application application); @Provides static SampleRepository repository(String name) { return new SampleRepository(name); } } @askashdavies

Slide 55

Slide 55 text

Dagger: Modules @Module abstract class ApplicationModule { @Binds abstract fun context(application: Application): Context @Module companion object { @Provides @JvmStatic fun repository(name: String): SampleRepository = SampleRepository(name) } } @askashdavies

Slide 56

Slide 56 text

Dagger: Modules public abstract class ApplicationModule { public static final ApplicationModule.Companion Companion = new ApplicationModule.Companion(); @Binds @NotNull public abstract Context context(@NotNull Application var1); @Provides @JvmStatic @NotNull public static final SampleRepository repository(@NotNull String name) { return Companion.repository(name); } @Module public static final class Companion { @Provides @JvmStatic @NotNull public final SampleRepository repository(@NotNull String name) { return new SampleRepository(name); } private Companion() { } } } @askashdavies

Slide 57

Slide 57 text

Dagger: Modules object ApplicationModule { @Provides @JvmStatic fun context(application: Application): Context = application @Provides @JvmStatic fun repository(name: String): SampleRepository = SampleRepository(name) } @askashdavies

Slide 58

Slide 58 text

Dagger: Modules public final class ApplicationModule { public static final ApplicationModule INSTANCE = new ApplicationModule(); @Provides @JvmStatic @NotNull public static final Context context(@NotNull Application application) { return (Context)application; } @Provides @JvmStatic @NotNull public static final SampleRepository repository(@NotNull String name) { return new SampleRepository(name); } private ApplicationModule() { } } @askashdavies

Slide 59

Slide 59 text

Dagger: Modules @file:JvmName("ApplicationModule") @file:Module @Provides fun context(application: Application): Context = application @Provides fun repository(name: String): SampleRepository = SampleRepository(name) @askashdavies

Slide 60

Slide 60 text

Dagger: Modules public final class ApplicationModule { @Provides @NotNull public static final Context context(@NotNull Application application) { return (Context)application; } @Provides @NotNull public static final SampleRepository repository(@NotNull String name) { return new SampleRepository(name); } } @askashdavies

Slide 61

Slide 61 text

No content

Slide 62

Slide 62 text

Kotlin: Generics @askashdavies

Slide 63

Slide 63 text

No content

Slide 64

Slide 64 text

Kotlin: Generics @askashdavies

Slide 65

Slide 65 text

Kotlin: Generics Java Interoperability @askashdavies

Slide 66

Slide 66 text

Kotlin: Generics Java Interoperability interface Collection extends Iterable { boolean addAll(Collection collection); } @askashdavies

Slide 67

Slide 67 text

Kotlin: Generics Java Interoperability interface Collection extends Iterable { boolean addAll(Collection collection); } @askashdavies

Slide 68

Slide 68 text

Kotlin: Generics Java Interoperability List : List @askashdavies

Slide 69

Slide 69 text

Kotlin: Generics Java Interoperability List : List @askashdavies

Slide 70

Slide 70 text

Kotlin: Generics Java Interoperability List strings = new ArrayList(); List objs = strings; objs.add(1); String string = strings.get(0); @askashdavies

Slide 71

Slide 71 text

Kotlin: Generics Java Interoperability List strings = new ArrayList(); List objs = strings; objs.add(1); String string = strings.get(0); // @askashdavies

Slide 72

Slide 72 text

Kotlin: Generics Java Interoperability interface Collection extends Iterable { boolean addAll(Collection collection); } @askashdavies

Slide 73

Slide 73 text

Kotlin: Generics Java Interoperability List box(String value) { /* ... */ } String unbox(List boxed) { /* ... */ } @askashdavies

Slide 74

Slide 74 text

Kotlin: Generics Java Interoperability class ListAdapter @Inject constructor(strings: List) @askashdavies

Slide 75

Slide 75 text

Kotlin: Generics Java Interoperability public final class ListAdapter { @Inject public ListAdapter(@NotNull List strings) { Intrinsics.checkParameterIsNotNull(strings, "strings"); super(); } } @askashdavies

Slide 76

Slide 76 text

Kotlin: Generics Dagger Multi-Binding @Module object ListModule { @IntoSet @Provides @JvmStatic fun hello(): String = "Hello" @IntoSet @Provides @JvmStatic fun world(): String = "World" } @askashdavies

Slide 77

Slide 77 text

Build Failed... ! @askashdavies

Slide 78

Slide 78 text

Kotlin: Generics Java Interoperability class ListAdapter @Inject constructor(strings: @JvmSuppressWildcards List) @askashdavies

Slide 79

Slide 79 text

Jetpack @askashdavies

Slide 80

Slide 80 text

Jetpack ViewModel @askashdavies

Slide 81

Slide 81 text

Jetpack ViewModel » Introduced at Google IO 2018 » Bootstrap Android development » Opinionated implementations » Break up support libraries » Migrate to androidx namespace @askashdavies

Slide 82

Slide 82 text

Jetpack ViewModel @askashdavies

Slide 83

Slide 83 text

Jetpack ViewModel » Android Application created » Android Activity created » Dagger @Component created » Androidx ViewModel created » Androidx Fragment created @askashdavies

Slide 84

Slide 84 text

Jetpack ViewModel » Android Application created ← » Android Activity created ! » Dagger @Component created ! » AndroidX ViewModel created ← » AndroidX Fragment created ← @askashdavies

Slide 85

Slide 85 text

⚠ Caution: A ViewModel must never reference a view, Lifecycle, or any class that may hold a reference to the activity context. @askashdavies

Slide 86

Slide 86 text

© 2019 Viacom International Inc.

Slide 87

Slide 87 text

© 2019 20th Century Fox

Slide 88

Slide 88 text

JetPack ViewModel class SampleViewModel @Inject constructor() : ViewModel { } class Activity : DaggerAppCompatActivity { @Inject lateinit var model: SampleViewModel } @askashdavies

Slide 89

Slide 89 text

JetPack ViewModel class SampleViewModel @Inject constructor() : ViewModel { } class Activity : DaggerAppCompatActivity { @Inject lateinit var model: SampleViewModel } @askashdavies

Slide 90

Slide 90 text

@askashdavies

Slide 91

Slide 91 text

Jetpack: ViewModel Dagger Multi-Binding @askashdavies

Slide 92

Slide 92 text

Jetpack: ViewModel Dagger Multi-Binding class ActivityViewModel @Inject constructor() : ViewModel() { } @askashdavies

Slide 93

Slide 93 text

Jetpack: ViewModel Dagger Multi-Binding @MapKey @Retention(RUNTIME) annotation class ViewModelKey(val value: KClass) @Module interface ActivityViewModelModule { @Binds @IntoMap @ViewModelKey(ViewModel::class) fun model(model: ActivityViewModel): ViewModel } @askashdavies

Slide 94

Slide 94 text

Jetpack: ViewModel Dagger Multi-Binding class ViewModelFactory @Inject constructor( private val creators: Map, @JvmSuppressWildcards Provider> ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create(kls: Class): T { var creator: Provider? = creators[kls] creator ?: creators.keys.firstOrNull(kls::isAssignableFrom)?.apply { creator = creators[this] } creator ?: throw IllegalArgumentException("Unrecognised class $kls") return creator.get() as T } } @askashdavies

Slide 95

Slide 95 text

Jetpack: ViewModel Dagger Multi-Binding class ViewModelActivity : DaggerAppCompatActivity { private lateinit var model: ActivityViewModel @Inject internal lateinit var factory: ViewModelFactory override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ... model = ViewModelProviders .of(this, factory) .get(ActivityViewModel::class.java) } } @askashdavies

Slide 96

Slide 96 text

Jetpack: ViewModel androidx.activity:activity-ktx:1.0.0 class ViewModelActivity : DaggerAppCompatActivity { private val model: ActivityViewModel by viewModels { factory } @Inject internal lateinit var factory: ViewModelFactory override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ... } } @askashdavies

Slide 97

Slide 97 text

Jetpack: ViewModel bit.ly/view-model-factory » Uses Dagger Multi-Binding to build map of Provider's » Global Factory to create all ViewModel's » Factory injected into Activity to create ViewModel » Complicated initial set-up configuration » Needs map binding @Module for every ViewModel » Application graph polluted with all Factory's @askashdavies

Slide 98

Slide 98 text

Good: HomeViewModelFactory class HomeViewModelFactory @Inject constructor( private val dataManager: DataManager, private val designerNewsLoginRepository: LoginRepository, private val sourcesRepository: SourcesRepository, private val dispatcherProvider: CoroutinesDispatcherProvider ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T { if (modelClass != HomeViewModel::class.java) { throw IllegalArgumentException("Unknown ViewModel class") } return HomeViewModel( dataManager, designerNewsLoginRepository, sourcesRepository, dispatcherProvider ) as T } } github.com/android/plaid/blob/master/app/src/main/java/io/plaidapp/ui/HomeViewModelFactory.kt

Slide 99

Slide 99 text

Not So Good: OtherViewModelFactory internal class OtherViewModelFactory @Inject constructor() : ViewModelProvider.Factory { @Inject lateinit var otherViewModel: OtherViewModel @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T { return if (modelClass.isAssignableFrom(OtherViewModel::class.java)) { otherViewModel as T } else { throw IllegalArgumentException( "Class ${modelClass.name} is not supported in this factory." ) } } } @askashdavies

Slide 100

Slide 100 text

© 2019 Warner Bros.

Slide 101

Slide 101 text

Jetpack: ViewModel bit.ly/view-model-provider internal class ViewModelFactory( private val provider: Provider ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T { return try { provider.get() as T } catch (exception: ClassCastException) { throw IllegalArgumentException( "Class ${modelClass.name} is not supported by this factory", exception ) } } } @askashdavies

Slide 102

Slide 102 text

Jetpack: ViewModel bit.ly/view-model-provider class ActivityViewModel @Inject constructor() : ViewModel() { class Factory @Inject constructor( provider: Provider ) : ViewModelFactory(provider) } class ViewModelActivity : DaggerAppCompatActivity { private val model: ActivityViewModel by viewModels { factory } @Inject internal lateinit var factory: ActivityViewModel.Factory } @askashdavies

Slide 103

Slide 103 text

@askashdavies

Slide 104

Slide 104 text

Android: *Factory » AppComponentFactory Android Pie ! » FragmentFactory fragmentx:1.1.0 » AbstractSavedStateVMFactory lifecycle-viewmodel-savedstate:1.0.0-alpha05 » LayoutInflater.Factory2 Android Cupcake " @askashdavies

Slide 105

Slide 105 text

Android: LayoutInflater.Factory2 github.com/square/AssistedInject class ImageLoaderView @AssistedInject constructor( @Assisted context: Context, @Assisted attrs: AttributeSet, private val loader: ImageLoader ) : ImageView(context, attrs) { @AssistedInject.Factory interface Factory { fun create( context: Context, attrs: AttributeSet ): ImageLoaderView } } @askashdavies

Slide 106

Slide 106 text

Android: LayoutInflater.Factory2 github.com/square/AssistedInject class ApplicationLayoutInflaterFactory @Inject constructor( private val imageLoaderFactory: ImageLoaderView.Factory ) : LayoutInflater.Factory { override fun onCreateView( name: String, context: Context, attrs: AttributeSet ): View? { if (name == ImageLoaderView::class.java.name) { return imageLoaderFactory.create(context, attrs) } return null } } @askashdavies

Slide 107

Slide 107 text

Kotlin: Experimental @askashdavies

Slide 108

Slide 108 text

Kotlin: Experimental Inline Classes » Wrapping types can introduce runtime overhead » Performance worse for primitive types » Initialised with single backing property » Inline classes represented by backing field at runtime » Sometimes represented as boxed type... @askashdavies

Slide 109

Slide 109 text

Kotlin: Experimental Inline Classes » Dagger recognises inline class as it's backing type » Module @Provide not complex enough to require wrapper » @Inject sites not complex enough to require wrapper » Can cause problems if backing type not qualified » Operates the same for typealias @askashdavies

Slide 110

Slide 110 text

@Binds @askashdavies

Slide 111

Slide 111 text

@Binds @Module interface MySuperAwesomeHappyFantasticModule { @Binds fun activity(activity: FragmentActivity): MySuperAwesomeHappyFantasticActivity } @askashdavies

Slide 112

Slide 112 text

@Binds @Module abstract class MySuperAwesomeHappyFantasticModule { @Binds abstract fun activity(activity: FragmentActivity): MySuperAwesomeHappyFantasticActivity } @askashdavies

Slide 113

Slide 113 text

@Binds @Module interface MySuperAwesomeHappyFantasticModule { @Binds fun activity(activity: FragmentActivity): MySuperAwesomeHappyFantasticActivity } @askashdavies

Slide 114

Slide 114 text

Defaults @askashdavies

Slide 115

Slide 115 text

@askashdavies

Slide 116

Slide 116 text

Inlined method bodies in Kotlin » Kotlin return types can be inferred from method body » Android Studio shows inlining return types » Return types to hide implementation detail easily missed » Interface vs Implemetation » Best practice to explicitly specify return type » Easier to review, easier to understand, avoids compiler errors » Framework types (Fragment.context) can be assumed nullable @askashdavies

Slide 117

Slide 117 text

Keeping internal implementation internal internal class Player class Game @Inject constructor( @Named("P1") private val player1: Player, @Named("P2") private val player2: Player ) @askashdavies

Slide 118

Slide 118 text

Keeping internal implementation internal internal class Player class Game @Inject internal constructor( @Named("P1") private val player1: Player, @Named("P2") private val player2: Player ) @askashdavies

Slide 119

Slide 119 text

Default Parameters? @askashdavies

Slide 120

Slide 120 text

Default Parameters? @askashdavies

Slide 121

Slide 121 text

@JvmOverloads @askashdavies

Slide 122

Slide 122 text

@JvmOverloads @askashdavies

Slide 123

Slide 123 text

Hope bit.ly/dagger-kotlin-support @askashdavies

Slide 124

Slide 124 text

Further Reading ! » Dave Leeds: Inline Classes and Autoboxing https://typealias.com/guides/inline-classes-and-autoboxing/ » Kotlin: Declaration Site Variance https://kotlinlang.org/docs/reference/generics.html#declaration-site-variance » Kotlin: Variant Generics https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html#variant-generics » Jake Wharton: Helping Dagger Help You https://jakewharton.com/helping-dagger-help-you/ » Dagger: Kotlin Dagger Best Practices https://github.com/google/dagger/issues/900 » Fred Porciúncula: Dagger 2 Official Guidelines https://proandroiddev.com/dagger-2-on-android-the-official-guidelines-you-should-be-following-2607fd6c002e » Warren Smith: Dagger & Kotlin https://medium.com/@naturalwarren/dagger-kotlin-3b03c8dd6e9b » Nazmul Idris: Advanced Dagger 2 w/ Android and Kotlin https://developerlife.com/2018/10/21/dagger2-and-kotlin/ @askashdavies

Slide 125

Slide 125 text

Thanks! @askashdavies