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.

Fc78fd09b8fee61efd4ef003fe104eb6?s=128

Ash Davies

October 05, 2020
Tweet

Transcript

  1. Ash Davies Android & Kotlin GDE Berlin @askashdavies Working with

    Dagger and Kotlin .droidcon - October
  2. What is DI?

  3. // Car.kt class Car { private val engine = Engine()

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

    { engine.start() } } fun main() { val engine = Engine() val car = Car(engine) car.start() }
  5. WTF? Dagger sounds confusing, I’ll just use something else...

  6. None
  7. None
  8. // ServiceLocator.kt object ServiceLocator { val engine: Engine get() =

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

    Car(private val engine = solarEngine) { fun start() { engine.start() } }
  10. • Dependency injection implementation • Generates code for injection •

    Compile time (no reflection) What is Dagger 2?
  11. In Java

  12. In Java, for Java

  13. In Java, for Java, by Java

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

  15. Java ☕

  16. Code Generation

  17. • AutoValue ⚙ • Lombok • Dagger Code Generation

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

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

  20. Kotlin noun - Freakin’ awesome

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

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

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

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

  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
  26. @Singleton != Singleton object Singleton ⚠ Tangent Alert

  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
  28. Kotlin private val viewModel by lazy(NONE) { LazyViewModel() } fun

    <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> = when (mode) { LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer) LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer) LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer) } Lazy ⚠ Tangent Alert
  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
  30. @Singleton Don’t do this! ⚠ Tangent Alert @ActivityScope class MainRepository

    @Inject constructor()
  31. Constructor Injection javax.inject.Named class Game @Inject constructor( @Named("P1") private val

    player1: Player, @Named("P2") private val player2: Player )
  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; } }
  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 )
  34. • Mutable (lateinit) properties • Annotation target unclear ‍♂ •

    Difficult to configure tests Field vs Constructor Injection • Immutable • Easy to use • Reliable injection • Compilation safety
  35. None
  36. • Application • Activity • BroadcastReceiver • ClassLoader • ContentProvider

    • Service Android Manifest Elements
  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
  38. class GameFragmentFactory : FragmentFactory() { override fun instantiate(classLoader: ClassLoader, className:

    String): Fragment { return super.instantiate(classLoader, className) } } FragmentFactory androidx.fragment.app
  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 } }
  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 } }
  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 )
  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) {...} ... }
  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:
  44. None
  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)
  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
  47. Working with Dagger and Kotlin @Modules

  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
  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) } } }
  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) } } }
  51. @Module Inline Method Body’s @Module object GameModule { @Provides fun

    gameController() = PlayerOne() } @Module object GameModule { @Provides fun gameController(): GameController = PlayerOne() }
  52. Generics<T> Boring computery stuff

  53. Generics<T> Java Interoperability • Wildcard types not available in Kotlin*

    • Declaration-site variance • Type projections
  54. interface Collection<E> extends Iterable<E> { boolean addAll(Collection<? extends E> collection);

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

  56. List<String> : List<Object> https://kotlinlang.org/docs/reference/generics.html#variance

  57. List<String> strings = new ArrayList<String>(); List<Object> objs = strings; objs.add(1);

    String string = strings.get(0); // https://kotlinlang.org/docs/reference/generics.html#variance
  58. interface Collection<E> extends Iterable<E> { boolean addAll(Collection<? extends E> collection);

    } https://kotlinlang.org/docs/reference/generics.html#variance
  59. List<String> box(String value) { ... } String unbox(List<? extends String>

    boxed) { ... }
  60. // ListAdapter.kt class ListAdapter @Inject constructor(strings: Set<String>)} // ListModule.kt @Module

    object ListModule { @IntoSet @Provides fun hello(): String = "Hello" @IntoSet @Provides fun world(): String = "World" }
  61. public final class ListAdapter { @Inject public ListAdapter(@NotNull List<? extends

    String> strings) { Intrinsics.checkParameterIsNotNull(strings, "strings"); super(); } } Build Failed…
  62. // ListAdapter.kt class ListAdapter @Inject constructor(strings: Set<@JvmSuppressWildcards String>)

  63. Kotlin Default Implementations interface ApplicationModule { @Provides @ActivityScope fun context(application:

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

    context(application: Application): Context = application }
  65. Kotlin ❌ Default Properties class Game @Inject constructor( private val

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

    Player = PlayerOne() )
  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
  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
  69. Working with Dagger and Kotlin... and Jetpack Hold on tight!

  70. • Introduced at Google IO Dev Keynote 2018 • Bootstrap

    Modern Android Development • Opinionated implementations • Break up support libraries • androidx namespace Jetpack ViewModel ⚠ Tangent Alert
  71. Jetpack ViewModel ⚠ Tangent Alert

  72. ⚠ Caution: A ViewModel must never reference a View, Lifecycle,

    or any class that may hold a reference to the activity Context.
  73. class GameViewModel @Inject constructor() : ViewModel() class GameActivity : DaggerAppCompatActivity

    { @Inject lateinit var viewModel: GameViewModel }
  74. class GameViewModel @Inject constructor() : ViewModel() { } @MapKey @Retention(RUNTIME)

    annotation class ViewModelKey(val value: KClass<out ViewModel>) @Module interface GameViewModelModule { @Binds @IntoMap @ViewModelKey(GameViewModel::class) fun model(model: GameViewModel): ViewModel } I’ll cover @ViewModelInject soon :)
  75. class ViewModelFactory @Inject constructor( private val creators: Map<Class<out ViewModel>, @JvmSuppressWildcards

    Provider<ViewModel>> ) : ViewModelProvider.Factory { override fun <T : ViewModel> create(kls: Class<T>): T { var creator: Provider<out ViewModel>? = creators[kls] creator ?: creators.keys .firstOrNull(kls::isAssignableFrom) ?.apply { creator = creators[this] } creator ?: throw IllegalArgumentException("Unrecognised class $kls") return creator.get() as T } }
  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
  77. internal class ViewModelFactory( private val provider: Provider<out ViewModel> ) :

    ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun <T : ViewModel> create(modelClass: Class<T>): T = try { provider.get() as T } catch (exception: ClassCastException) { throw IllegalArgumentException( "Class ${modelClass.name} is not supported by this factory", exception ) } }
  78. class GameActivity : DaggerAppCompatActivity { private val model: GameViewModel by

    viewModels { factory } @Inject internal lateinit var factory: ViewModelFactory<GameViewModel> override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ... } }
  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 ); }
  80. dagger.android @ContributesAndroidInjector dagger.dev/dev-guide/android

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

  82. None
  83. Dagger: Hilt Hope...

  84. Dagger: Hilt Components

  85. @Module @InstallIn(SingletonComponent::class) object class FooModule { // SingletonComponent has access

    to Application binding @Provides fun provideBar(app: Application): Bar {...} }
  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? } }
  87. Dagger Hilt dagger.hilt.android.plugin @HiltAndroidApp(MultiDexApplication::class) class MyApplication : Hilt_MyApplication()

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

  89. class GameViewModel @ViewModelInject constructor( @Assisted handle: SavedStateHandle, listing: GameListing )

    @AndroidEntryPoint class GameActivity : AppCompatActivity { private val viewModel: GameViewModel by viewModels() }
  90. Anvil Project Hephaestus developer.squareup.com/blog/introducing-anvil github.com/square/anvil

  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
  92. Ash Davies Android & Kotlin GDE Berlin @askashdavies Thank You!