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

Droidcon Italy: Working with Dagger and Kotlin

Droidcon Italy: Working with Dagger and Kotlin

Dagger has become one of the most widely used libraries in Android development, and is often one of the first to be thought of when bootstrapping a new project, but there are still many nuances and caveats that often get overlooked!

Many questions arose after the adoption of Kotlin on how to continue following best practices from Java, so whether you’re using dagger-android or vanilla Dagger, I’ll go through some tips and tricks to make sure you’re using Dagger to its best capacity.

Ash Davies

October 05, 2020
Tweet

More Decks by Ash Davies

Other Decks in Programming

Transcript

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

    View Slide

  2. What is DI?

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  6. View Slide

  7. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  11. In Java

    View Slide

  12. In Java, for Java

    View Slide

  13. In Java, for Java, by Java

    View Slide

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

    View Slide

  15. Java ☕

    View Slide

  16. Code Generation

    View Slide

  17. ● AutoValue ⚙
    ● Lombok
    ● Dagger
    Code Generation

    View Slide

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

    View Slide

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

    View Slide

  20. Kotlin
    noun
    - Freakin’ awesome

    View Slide

  21. Working with Dagger
    and Kotlin
    A Love-Hate Story.

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  25. @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

    View Slide

  26. @Singleton != Singleton
    object Singleton
    ⚠ Tangent Alert

    View Slide

  27. @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

    View Slide

  28. 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

    View Slide

  29. 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

    View Slide

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

    View Slide

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

    View Slide

  32. 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;
    }
    }

    View Slide

  33. 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
    )

    View Slide

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

    View Slide

  35. View Slide

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

    View Slide

  37. ● 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  41. 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
    )

    View Slide

  42. 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) {...}
    ...
    }

    View Slide

  43. 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:

    View Slide

  44. View Slide

  45. 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)

    View Slide

  46. 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

    View Slide

  47. Working with Dagger
    and Kotlin
    @Modules

    View Slide

  48. @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

    View Slide

  49. @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)
    }
    }
    }

    View Slide

  50. @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)
    }
    }
    }

    View Slide

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

    View Slide

  52. Generics
    Boring computery stuff

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  59. List box(String value) {
    ...
    }
    String unbox(List extends String> boxed) {
    ...
    }

    View Slide

  60. // 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"
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  67. ● 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

    View Slide

  68. ● 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

    View Slide

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

    View Slide

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

    View Slide

  71. Jetpack
    ViewModel
    ⚠ Tangent Alert

    View Slide

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

    View Slide

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

    View Slide

  74. 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 :)

    View Slide

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

    View Slide

  76. 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

    View Slide

  77. 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
    )
    }
    }

    View Slide

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

    View Slide

  79. 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
    );
    }

    View Slide

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

    View Slide

  81. youtube.com/watch?v=o-ins1nvbDg

    View Slide

  82. View Slide

  83. Dagger: Hilt
    Hope...

    View Slide

  84. Dagger: Hilt
    Components

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  91. 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

    View Slide

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

    View Slide