Slide 1

Slide 1 text

Ash Davies Android & Kotlin GDE Berlin @askashdavies Working with Dagger and Kotlin .droidcon - October

Slide 2

Slide 2 text

What is DI?

Slide 3

Slide 3 text

// Car.kt class Car { private val engine = Engine() fun start() { engine.start() } } fun main() { val car = Car() car.start() }

Slide 4

Slide 4 text

// Car.kt class Car(private val engine: Engine) { fun start() { engine.start() } } fun main() { val engine = Engine() val car = Car(engine) car.start() }

Slide 5

Slide 5 text

WTF? Dagger sounds confusing, I’ll just use something else...

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

// ServiceLocator.kt object ServiceLocator { val engine: Engine get() = Engine() } // Car.kt class Car { private val engine = ServiceLocator.engine fun start() { engine.start() } }

Slide 9

Slide 9 text

// CarModule.kt val solarEngine get() = SolarEngine() // Car.kt class Car(private val engine = solarEngine) { fun start() { engine.start() } }

Slide 10

Slide 10 text

● Dependency injection implementation ● Generates code for injection ● Compile time (no reflection) What is Dagger 2?

Slide 11

Slide 11 text

In Java

Slide 12

Slide 12 text

In Java, for Java

Slide 13

Slide 13 text

In Java, for Java, by Java

Slide 14

Slide 14 text

In Java, for Java, by Java, with Java software engineers...

Slide 15

Slide 15 text

Java ☕

Slide 16

Slide 16 text

Code Generation

Slide 17

Slide 17 text

● AutoValue ⚙ ● Lombok ● Dagger Code Generation

Slide 18

Slide 18 text

Helping Dagger Help You jakewharton.com/helping-dagger-help-you/ Jake Wharton (Droidcon London ‘18)

Slide 19

Slide 19 text

Guice (Pronounced 'juice') Dagger (JSR-330) Dagger 2 (Sans Reflection) History

Slide 20

Slide 20 text

Kotlin noun - Freakin’ awesome

Slide 21

Slide 21 text

Working with Dagger and Kotlin A Love-Hate Story.

Slide 22

Slide 22 text

@Retention java.lang.annotation Use Kotlin retention annotations instead of Java retention

Slide 23

Slide 23 text

@Qualifier javax.inject Used to identify dependencies with identical signatures

Slide 24

Slide 24 text

@Scope javax.inject Governs dependency reuse within a user defined scope

Slide 25

Slide 25 text

@Singleton javax.inject Annotate an @Provides method or injectable class with @Singleton. The graph will use a single instance of the value for all of its clients. ⚠ Tangent Alert

Slide 26

Slide 26 text

@Singleton != Singleton object Singleton ⚠ Tangent Alert

Slide 27

Slide 27 text

@Singleton class Singleton { private Singleton instance; public Helper getInstance() { if (instance == null) { synchronized (this) { if (instance == null) { instance = new Singleton(); } } } return instance; } } Double-checked locking ⚠ Tangent Alert

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

Favour @Reusable Use @Scope for stateful dependencies ⚠ Tangent Alert ● Great for expensive dependencies ● Work great in single thread environments ● Not guaranteed same instance in multiple threads ● Prefer to keep your Dagger graph stateless

Slide 30

Slide 30 text

@Singleton Don’t do this! ⚠ Tangent Alert @ActivityScope class MainRepository @Inject constructor()

Slide 31

Slide 31 text

Constructor Injection javax.inject.Named class Game @Inject constructor( @Named("P1") private val player1: Player, @Named("P2") private val player2: Player )

Slide 32

Slide 32 text

Constructor Injection javax.inject.Named 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; } }

Slide 33

Slide 33 text

Field Injection: lateinit var javax.inject.Named class Game @Inject constructor() { @Inject @Named("P1") private val player1: Player, @Inject @Named("P2") private val player2: Player )

Slide 34

Slide 34 text

● Mutable (lateinit) properties ● Annotation target unclear ‍♂ ● Difficult to configure tests Field vs Constructor Injection ● Immutable ● Easy to use ● Reliable injection ● Compilation safety

Slide 35

Slide 35 text

No content

Slide 36

Slide 36 text

● Application ● Activity ● BroadcastReceiver ● ClassLoader ● ContentProvider ● Service Android Manifest Elements

Slide 37

Slide 37 text

● instantiateApplication(ClassLoader, String) ● instantiateActivity(ClassLoader, String, Intent) ● instantiateClassLoader(ClassLoader, ApplicationInfo) ● instantiateService(ClassLoader, String, Intent) ● instantiateReceiver(ClassLoader, String, Intent) ● instantiateProvider(ClassLoader, String) Android 9 (API 28) AppComponentFactory

Slide 38

Slide 38 text

class GameFragmentFactory : FragmentFactory() { override fun instantiate(classLoader: ClassLoader, className: String): Fragment { return super.instantiate(classLoader, className) } } FragmentFactory androidx.fragment.app

Slide 39

Slide 39 text

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 } }

Slide 40

Slide 40 text

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 } }

Slide 41

Slide 41 text

Field Injection: lateinit var javax.inject.Named class Game @Inject constructor() { @Inject @Named("P1") private val player1: Player, @Inject @Named("P2") private val player2: Player )

Slide 42

Slide 42 text

Field Injection: lateinit var javax.inject.Named 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) {...} ... }

Slide 43

Slide 43 text

Field Injection: lateinit var javax.inject.Named class Game @Inject constructor() { @Inject @field:Named("P1") lateinit var player1: Player @Inject @field:Named("P2") lateinit var player2: Player } @field: @set: @get: @param: @property: @setparam: @receiver: @delegate:

Slide 44

Slide 44 text

No content

Slide 45

Slide 45 text

Dagger 2.25.2 Released 23rd Oct 2019 ● Qualifier annotations on fields can now be understood without the need for @field:MyQualifier (646e033) ● @Module object classes no longer need @JvmStatic on the provides methods. (0da2180)

Slide 46

Slide 46 text

Dagger 2.28 Released 10th Jun 2020 ● Using a redundant field: site target in injected properties ● Using a redundant @JvmStatic in object modules. ● Annotating companion objects with @Module

Slide 47

Slide 47 text

Working with Dagger and Kotlin @Modules

Slide 48

Slide 48 text

@Module Why can’t @Binds and instance @Provides methods go in the same module? @Module abstract class GameModule { @Binds abstract GameController playerOne(PlayerOne playerOne); @Provides static InputChannel inputChannel(GameController gameController) { return ControllerInputChannel(gameController); } } dagger.dev/dev-guide/faq.html#why-cant-binds-and-instance-provides-methods-go-in-the-same-module

Slide 49

Slide 49 text

@Module Why can’t @Binds and instance @Provides methods go in the same module? @Module abstract class GameModule { @Binds abstract fun gameController(playerOne: PlayerOne): GameController @Module companion object { @Provides @JvmStatic fun inputChannel(gameController: GameController): InputChannel { return ControllerInputChannel(gameController) } } }

Slide 50

Slide 50 text

@Module Dagger 2.25.2 - 2.26 @Module interface GameModule { @Binds fun gameController(playerOne: PlayerOne): GameController companion object { @Provides fun inputChannel(gameController: GameController): InputChannel { return ControllerInputChannel(gameController) } } }

Slide 51

Slide 51 text

@Module Inline Method Body’s @Module object GameModule { @Provides fun gameController() = PlayerOne() } @Module object GameModule { @Provides fun gameController(): GameController = PlayerOne() }

Slide 52

Slide 52 text

Generics Boring computery stuff

Slide 53

Slide 53 text

Generics Java Interoperability ● Wildcard types not available in Kotlin* ● Declaration-site variance ● Type projections

Slide 54

Slide 54 text

interface Collection extends Iterable { boolean addAll(Collection collection); } https://kotlinlang.org/docs/reference/generics.html#variance

Slide 55

Slide 55 text

interface Collection extends Iterable { boolean addAll(Collection collection); } https://kotlinlang.org/docs/reference/generics.html#variance

Slide 56

Slide 56 text

List : List https://kotlinlang.org/docs/reference/generics.html#variance

Slide 57

Slide 57 text

List strings = new ArrayList(); List objs = strings; objs.add(1); String string = strings.get(0); // https://kotlinlang.org/docs/reference/generics.html#variance

Slide 58

Slide 58 text

interface Collection extends Iterable { boolean addAll(Collection collection); } https://kotlinlang.org/docs/reference/generics.html#variance

Slide 59

Slide 59 text

List box(String value) { ... } String unbox(List boxed) { ... }

Slide 60

Slide 60 text

// ListAdapter.kt class ListAdapter @Inject constructor(strings: Set)} // ListModule.kt @Module object ListModule { @IntoSet @Provides fun hello(): String = "Hello" @IntoSet @Provides fun world(): String = "World" }

Slide 61

Slide 61 text

public final class ListAdapter { @Inject public ListAdapter(@NotNull List strings) { Intrinsics.checkParameterIsNotNull(strings, "strings"); super(); } } Build Failed…

Slide 62

Slide 62 text

// ListAdapter.kt class ListAdapter @Inject constructor(strings: Set<@JvmSuppressWildcards String>)

Slide 63

Slide 63 text

Kotlin Default Implementations interface ApplicationModule { @Provides @ActivityScope fun context(application: Application): Context = application }

Slide 64

Slide 64 text

Kotlin ❌ Default Implementations interface ApplicationModule { @Provides @ActivityScope fun context(application: Application): Context = application }

Slide 65

Slide 65 text

Kotlin ❌ Default Properties class Game @Inject constructor( private val player: Player = PlayerOne() )

Slide 66

Slide 66 text

Kotlin ❌ @JvmDefault class Game @Inject constructor( private val player: Player = PlayerOne() )

Slide 67

Slide 67 text

● 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... Kotlin Inline classes

Slide 68

Slide 68 text

● 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 Kotlin ❌ Inline classes

Slide 69

Slide 69 text

Working with Dagger and Kotlin... and Jetpack Hold on tight!

Slide 70

Slide 70 text

● Introduced at Google IO Dev Keynote 2018 ● Bootstrap Modern Android Development ● Opinionated implementations ● Break up support libraries ● androidx namespace Jetpack ViewModel ⚠ Tangent Alert

Slide 71

Slide 71 text

Jetpack ViewModel ⚠ Tangent Alert

Slide 72

Slide 72 text

⚠ Caution: A ViewModel must never reference a View, Lifecycle, or any class that may hold a reference to the activity Context.

Slide 73

Slide 73 text

class GameViewModel @Inject constructor() : ViewModel() class GameActivity : DaggerAppCompatActivity { @Inject lateinit var viewModel: GameViewModel }

Slide 74

Slide 74 text

class GameViewModel @Inject constructor() : ViewModel() { } @MapKey @Retention(RUNTIME) annotation class ViewModelKey(val value: KClass) @Module interface GameViewModelModule { @Binds @IntoMap @ViewModelKey(GameViewModel::class) fun model(model: GameViewModel): ViewModel } I’ll cover @ViewModelInject soon :)

Slide 75

Slide 75 text

class ViewModelFactory @Inject constructor( private val creators: Map, @JvmSuppressWildcards Provider> ) : ViewModelProvider.Factory { 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 } }

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

internal class ViewModelFactory( private val provider: Provider ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T = try { provider.get() as T } catch (exception: ClassCastException) { throw IllegalArgumentException( "Class ${modelClass.name} is not supported by this factory", exception ) } }

Slide 78

Slide 78 text

class GameActivity : DaggerAppCompatActivity { private val model: GameViewModel by viewModels { factory } @Inject internal lateinit var factory: ViewModelFactory override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ... } }

Slide 79

Slide 79 text

class Thermosiphon implements Pump { private final Heater heater; @Inject Thermosiphon(Heater heater) { this.heater = heater; } ... } class CoffeeMaker { @Inject Heater heater; @Inject Pump pump; ... } @Module interface HeaterModule { @Binds abstract Heater bindHeater( ElectricHeater impl ); }

Slide 80

Slide 80 text

dagger.android @ContributesAndroidInjector dagger.dev/dev-guide/android

Slide 81

Slide 81 text

youtube.com/watch?v=o-ins1nvbDg

Slide 82

Slide 82 text

No content

Slide 83

Slide 83 text

Dagger: Hilt Hope...

Slide 84

Slide 84 text

Dagger: Hilt Components

Slide 85

Slide 85 text

@Module @InstallIn(SingletonComponent::class) object class FooModule { // SingletonComponent has access to Application binding @Provides fun provideBar(app: Application): Bar {...} }

Slide 86

Slide 86 text

@HiltAndroidApp class GameApplication : BaseApplication() { @Inject lateinit var game: Game override fun onCreate() { super.onCreate() // Injection happens here // Do you want to play a $game? } }

Slide 87

Slide 87 text

Dagger Hilt dagger.hilt.android.plugin @HiltAndroidApp(MultiDexApplication::class) class MyApplication : Hilt_MyApplication()

Slide 88

Slide 88 text

Dagger Hilt @ViewModelInject implementation ‘androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha02’ kapt 'androidx.hilt:hilt-compiler:1.0.0-alpha02'

Slide 89

Slide 89 text

class GameViewModel @ViewModelInject constructor( @Assisted handle: SavedStateHandle, listing: GameListing ) @AndroidEntryPoint class GameActivity : AppCompatActivity { private val viewModel: GameViewModel by viewModels() }

Slide 90

Slide 90 text

Anvil Project Hephaestus developer.squareup.com/blog/introducing-anvil github.com/square/anvil

Slide 91

Slide 91 text

Manuel Vivo: An Opinionated Guide to Dependency Injection on Android youtube.com/watch?v=o-ins1nvbDg Jake Wharton: Helping Dagger Help You jakewharton.com/helping-dagger-help-you/ Dagger: Kotlin Dagger Best Practices github.com/google/dagger/issues/900 Fred Porciúncula: Dagger 2 Official Guidelines proandroiddev.com/dagger-2-on-android-the-official-guidelines-you-should-be-following-2607fd6c002e Zac Sweers: Dagger Party tricks zacsweers.dev/dagger-party-tricks-deferred-okhttp-init/ Further Reading

Slide 92

Slide 92 text

Ash Davies Android & Kotlin GDE Berlin @askashdavies Thank You!