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

Dagger 2 - In Practice

Dagger 2 - In Practice

Pandora’s Android codebase is 5+ years old. The project was initially developed by a handful engineers who were tasked with creating and delivering new features quickly. Over time, it evolved without a clear dependency management pattern. With a codebase of approximately half a million lines of code, and a growing team continuously developing features, it seemed daunting to suddenly refactor every component to properly utilize a dependency injection framework. Earlier this year Pandora was able to add Dagger 2 to its codebase.

In this talk, Pandora’s engineers will share details on how they managed to successfully refactor the application to use dependency injection while simultaneously continuing development across the app. Additionally, obstacles and solutions along with developed patterns will be shared in the hopes that other app developers take something useful back. Types of Injections, Scopes and Testing are a few of the topics that will be covered.

Daniel Santiago

August 30, 2016
Tweet

Other Decks in Programming

Transcript

  1. Things I’ll talk about Dependency Injection Dagger 2 - The

    Basics Dagger 2 - Behind the Scenes Adopting Dagger Developed Patterns Testing Q&A
  2. Pandora We play music! We been playing music on Android

    for a while. Since 2009! We are growing and we want to keep playing music. Started with 2 Android Devs. Up to 20+ devs now.
  3. Pandora Only 1 major refactor: Splitting UI / App code

    from ‘Radio’ and creating a library. Around 3 years ago. Early this year we introduced Dependency Injection with Dagger 2.
  4. Dependency Injection It's a software design pattern. Increases modularity of

    the app and makes it extensible. Lots of framework, old and new: • Spring • Guice • Dagger1 • Dagger2
  5. No DI class Computer { private final Processor cpu; private

    final Memory ram; private final PowerSupply psu; private final Motherboard mb; public Computer() { this.cpu = new x32CPU(); this.ram = new DDR2(256); this.psu = new BronzePSU(); this.mb = new MiniATX(cpu, ram); } }
  6. No DI class Computer { private final Processor cpu; private

    final Memory ram; private final PowerSupply psu; private final Motherboard mb; public Computer() { this.cpu = new x32CPU(); this.ram = new DDR2(256); this.psu = new BronzePSU(); this.mb = new MiniATX(cpu, ram); } } We need these to make a ‘working’ PC.
  7. No DI class Main { public static void main(String[] args)

    { Computer superComputer = new Computer(); String answer = superComputer.compute("Should I use Enums?"); } }
  8. With DI class Computer { private final Processor cpu; private

    final Memory ram; private final PowerSupply psu; private final Motherboard mb; public Computer(Processor cpu, Memory memory, PowerSupply psu, Motherboard mb) { this.cpu = cpu; this.ram = memory; this.psu = psu; this.mb = mb; }
  9. With DI class Computer { private final Processor cpu; private

    final Memory ram; private final PowerSupply psu; private final Motherboard mb; public Computer(Processor cpu, Memory memory, PowerSupply psu, Motherboard mb) { this.cpu = cpu; this.ram = memory; this.psu = psu; this.mb = mb; } Dependencies are constructor arguments.
  10. With DI class Main { public static void main(String[] args)

    { Processor cpu = new x32CPU(); Memory ram = new DDR2(256); PowerSupply psu = new BronzePSU(); Motherboard mb = new MiniATX(cpu, ram); Computer superComputer = new Computer(cpu, ram, psu, mb); String answer = superComputer.compute("Should I use Enums?"); } }
  11. DI + Some Framework class Main { public static void

    main(String[] args) { // ... Computer superComputer = injector.getComputer(); String answer = superComputer.compute("Should I use Enums?"); } }
  12. DI + Some Framework class Main { public static void

    main(String[] args) { // ... Computer superComputer = injector.getComputer(); String answer = superComputer.compute("Should I use Enums?"); } }
  13. Why use DI? Better unit testing. Dependencies can be replaced

    with mock. @Test public void testCompute() { Processor mockCPU = Mockito.mock(Processor.class); Memory mockRAM = Mockito.mock(Memory.class); PowerSupply mockPSU = Mockito.mock(PowerSupply.class); Motherboard mockMB = Mockito.mock(Motherboard.class); Computer superComputer = new Computer(mockCPU, mockRAM, mockPSU, mockMB); String answer = superComputer.compute("What's the meaning of life?"); // ... verify stuff assertEquals("42", answer); }
  14. Why use DI? Better unit testing. Dependencies can be replaced

    with mock. @Test public void testCompute() { Processor mockCPU = Mockito.mock(Processor.class); Memory mockRAM = Mockito.mock(Memory.class); PowerSupply mockPSU = Mockito.mock(PowerSupply.class); Motherboard mockMB = Mockito.mock(Motherboard.class); Computer superComputer = new Computer(mockCPU, mockRAM, mockPSU, mockMB); String answer = superComputer.compute("What's the meaning of life?"); // ... verify stuff assertEquals("42", answer); }
  15. Why use DI? Better unit testing. Dependencies can be replaced

    with mock. @Test public void testCompute() { Processor mockCPU = Mockito.mock(Processor.class); Memory mockRAM = Mockito.mock(Memory.class); PowerSupply mockPSU = Mockito.mock(PowerSupply.class); Motherboard mockMB = Mockito.mock(Motherboard.class); Computer superComputer = new Computer(mockCPU, mockRAM, mockPSU, mockMB); String answer = superComputer.compute("What's the meaning of life?"); // ... verify stuff assertEquals("42", answer); }
  16. Why use DI? Better unit testing. Dependencies can be replaced

    with mock. @Test public void testCompute() { Processor mockCPU = Mockito.mock(Processor.class); Memory mockRAM = Mockito.mock(Memory.class); PowerSupply mockPSU = Mockito.mock(PowerSupply.class); Motherboard mockMB = Mockito.mock(Motherboard.class); Computer superComputer = new Computer(mockCPU, mockRAM, mockPSU, mockMB); String answer = superComputer.compute("What's the meaning of life?"); // ... verify stuff assertEquals("42", answer); }
  17. Why use DI? Easily internals replacement. x64 CPU, DDR3, Gold

    Rated PSU, ATX Motherboard. class Main { public static void main(String[] args) { Processor cpu = new x64CPU(); Memory ram = new DDR3(16384); PowerSupply psu = new GoldPSU(); Motherboard mb = new ATX(cpu, ram); Computer superComputer = new Computer(cpu, ram, psu, mb); String answer = superComputer.compute("2 + 2?"); } }
  18. Why use DI? No assumptions about how internals works. Does

    the CPU do math? Good. Does the Computer need to know how? No. CPU ALU Cache BUS CU Registers I/O PC
  19. Why use DI? Extensibility. public Computer(Processor cpu, Memory memory, PowerSupply

    psu, Motherboard mb, VideoCard vga) { this.cpu = cpu; this.ram = memory; this.psu = psu; this.mb = mb; this.vga = vga; } class Main { public static void main(String[] args) { // ... Computer superComputer = injector.getComputer(); String answer = superComputer.compute("Should I use Enums?"); } }
  20. Dagger 2 Dependency Injection Framework • No runtime library. •

    No reflection. • Validates all at compile time. • Generates code. Forked from Square’s Dagger 1. Dagger 2 is maintained by Google.
  21. Dagger 2 - The Basics @Component - Injector or dependency

    graph. Connects modules with injections. @Module + @Provides - Annotation for defining and providing dependencies. @Inject - Annotations for requesting dependencies. + Lots of awesome magic.
  22. Dagger 2 - Defining Dependencies @Module public class ComputerModule {

    @Provides Processor provideCPU() { return new x64CPU(); } }
  23. Dagger 2 - Defining Dependencies @Module public class ComputerModule {

    @Provides Processor provideCPU() { return new x64CPU(); } @Provides Memory provideRAM() { return new DDR3(memorySize); } }
  24. Dagger 2 - Defining Dependencies @Module public class ComputerModule {

    private final int memorySize; public ComputerModule(int memorySize) { this.memorySize = memorySize; } @Provides Processor provideCPU() { return new x64CPU(); } @Provides Memory provideRAM() { return new DDR3(memorySize); }
  25. Dagger 2 - Defining Dependencies @Module public class ComputerModule {

    private final int memorySize; public ComputerModule(int memorySize) { this.memorySize = memorySize; } @Provides Processor provideCPU() { return new x64CPU(); } @Provides Memory provideRAM() { return new DDR3(memorySize); }
  26. Dagger 2 - Defining Dependencies // ... @Provides PowerSupply providePowerSupply()

    { return new GoldPSU(); } @Provides Motherboard provideMB(Processor cpu, Memory ram) { return new ATX(cpu, ram); } }
  27. Dagger 2 - Defining Dependencies // ... @Provides PowerSupply providePowerSupply()

    { return new GoldPSU(); } @Provides Motherboard provideMB(Processor cpu, Memory ram) { return new ATX(cpu, ram); } } Dagger will try to satisfy these.
  28. Dagger 2 - Defining Dependencies // ... @Provides PowerSupply providePowerSupply()

    { return new GoldPSU(); } @Provides Motherboard provideMB(Processor cpu, Memory ram) { return new ATX(cpu, ram); } @Provides Computer provideComputer(PowerSupply psu, Motherboard mb) { return new Computer(mb.getCPU(), mb.getRAM(), psu, mb); } }
  29. Dagger 2 - Defining Dependencies // ... @Provides PowerSupply providePowerSupply()

    { return new GoldPSU(); } @Provides Motherboard provideMB(Processor cpu, Memory ram) { return new ATX(cpu, ram); } @Provides Computer provideComputer(PowerSupply psu, Motherboard mb) { return new Computer(mb.getCPU(), mb.getRAM(), psu, mb); } } @Provides methods are flexible.
  30. Dagger 2 - The Glue @Component(modules = { ComputerModule.class })

    public interface ShinyGamesComponent { Computer getPC(); }
  31. Dagger 2 - The Glue @Component(modules = { ComputerModule.class })

    public interface ShinyGamesComponent { Computer getPC(); } Components can have multiple modules.
  32. Dagger 2 - The Glue @Component(modules = { ComputerModule.class })

    public interface ShinyGamesComponent { Computer getPC(); } Components can also be an abstract class.
  33. Dagger 2 - The Glue @Component(modules = { ComputerModule.class })

    public interface ShinyGamesComponent { Computer getPC(); } Getter to expose computers.
  34. Dagger 2 - The Glue public static void main(String[] args)

    { ShinyGamesComponent component = DaggerShinyGamesComponent.builder() .computerModule(new ComputerModule(16384)) .build(); Computer pc = component.getPC(); // ... use computer to create games! }
  35. Dagger 2 - The Glue public static void main(String[] args)

    { ShinyGamesComponent component = DaggerShinyGamesComponent.builder() .computerModule(new ComputerModule(16384)) .build(); Computer pc = component.getPC(); // ... use computer to create games! } Dagger generated class.
  36. Dagger 2 - The Glue public static void main(String[] args)

    { ShinyGamesComponent component = DaggerShinyGamesComponent.builder() .computerModule(new ComputerModule(16384)) .build(); Computer pc = component.getPC(); // ... use computer to create games! } Modules with no-args constructors can be omitted.
  37. Dagger 2 - The Glue public static void main(String[] args)

    { ShinyGamesComponent component = DaggerShinyGamesComponent.builder() .computerModule(new ComputerModule(16384)) .build(); Computer pc = component.getPC(); // ... use computer to create games! }
  38. Dagger 2 - The Glue public static void main(String[] args)

    { ShinyGamesComponent component = DaggerShinyGamesComponent.builder() .computerModule(new ComputerModule(16384)) .build(); Computer pc = component.getPC(); // ... use computer to create games! }
  39. Dagger 2 - Field Injection public class MainActivity extends Activity

    { @Inject Computer computer; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); component.inject(this); } }
  40. Dagger 2 - Field Injection public class MainActivity extends Activity

    { @Inject Computer computer; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); component.inject(this); } } Field can’t be private nor final.
  41. Dagger 2 - Field Injection public class MainActivity extends Activity

    { @Inject Computer computer; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); component.inject(this); } } Use injector to populate fields.
  42. Dagger 2 - Construction Injection @Provides Motherboard provideMB(Processor cpu, Memory

    ram) { return new ATX(cpu, ram); } Provider definition is essentially contruction injection.
  43. Dagger 2 - Construction Injection @Inject public ATX(Processor cpu, Memory

    ram) { this.cpu = cpu; this.ram = ram; } Alternative construction injection. Note: Provider methods takes precedence!
  44. Dagger 2 - Construction Injection @Inject public ATX(Processor cpu, Memory

    ram) { this.cpu = cpu; this.ram = ram; } Alternative construction injection. Note: Provider methods takes precedence!
  45. Dagger 2 - Singletons @Provides @Singleton Cluster provideCluster(Provider<Computer> computerProvider) {

    List<Computer> computers = new ArrayList<>(); for (int i = 0; i < clusterSize; i++) { computers.add(computerProvider.get()); } return new Cluster(computers); }
  46. Dagger 2 - Singletons @Provides @Singleton Cluster provideCluster(Provider<Computer> computerProvider) {

    List<Computer> computers = new ArrayList<>(); for (int i = 0; i < clusterSize; i++) { computers.add(computerProvider.get()); } return new Cluster(computers); }
  47. Dagger 2 - Singletons @Provides @Singleton Cluster provideCluster(Provider<Computer> computerProvider) {

    List<Computer> computers = new ArrayList<>(); for (int i = 0; i < clusterSize; i++) { computers.add(computerProvider.get()); } return new Cluster(computers); } Dagger can satisfy factory dependencies.
  48. Dagger 2 - Singletons @Provides @Singleton Cluster provideCluster(Provider<Computer> computerProvider) {

    List<Computer> computers = new ArrayList<>(); for (int i = 0; i < clusterSize; i++) { computers.add(computerProvider.get()); } return new Cluster(computers); } A new computer is created on every get() call.
  49. Dagger 2 - Singletons @Provides @Singleton Cluster provideCluster(Provider<Computer> computerProvider) {

    List<Computer> computers = new ArrayList<>(); for (int i = 0; i < clusterSize; i++) { computers.add(computerProvider.get()); } return new Cluster(computers); } Only one instance of Cluster is created for the Component.
  50. Dagger 2 - Singletons Not the traditional java singleton pattern

    (static field + static getter). @Singleton - Dagger 2 built-in @Scope annotation. The ‘singleton’ instance is within the Component, a new Component will create a new ‘singleton’ instance. Remember to also annotate your Component with the Scoped annotation. @Singleton @Component(modules = ComputerModule.class) public interface ShinyGamesComponent {
  51. Dagger 2 - Subcomponent @Subcomponent(modules = GameModule.class) public interface GameComponent

    { @Named("Player") String getPlayerName(); @Named("World") String getWorldName(); } • Inherits parent bindings. • Shorter life-time than the parent. • Annotated with @Subcomponent
  52. Dagger 2 - Subcomponent @Subcomponent(modules = GameModule.class) public interface GameComponent

    { @Named("Player") String getPlayerName(); @Named("World") String getWorldName(); } • Inherits parent bindings. • Shorter life-time than the parent. • Annotated with @Subcomponent
  53. Dagger 2 - Subcomponent @Module public class GameModule { private

    String seed; public GameModule(String seed) { this.seed = seed; } @Provides @Named("Player") String providePlayer(Cluster cluster) { return cluster.generatePlayerName(); } @Provides @Named("World") String provideWorldName(Cluster cluster) { return cluster.generateWorldName(seed);
  54. Dagger 2 - Subcomponent @Module public class GameModule { private

    String seed; public GameModule(String seed) { this.seed = seed; } @Provides @Named("Player") String providePlayer(Cluster cluster) { return cluster.generatePlayerName(); } @Provides @Named("World") String provideWorldName(Cluster cluster) { return cluster.generateWorldName(seed);
  55. Dagger 2 - Subcomponent @Module public class GameModule { private

    String seed; public GameModule(String seed) { this.seed = seed; } @Provides @Named("Player") String providePlayer(Cluster cluster) { return cluster.generatePlayerName(); } @Provides @Named("World") String provideWorldName(Cluster cluster) { return cluster.generateWorldName(seed);
  56. Dagger 2 - Subcomponent @Module public class GameModule { private

    String seed; public GameModule(String seed) { this.seed = seed; } @Provides @Named("Player") String providePlayer(Cluster cluster) { return cluster.generatePlayerName(); } @Provides @Named("World") String provideWorldName(Cluster cluster) { return cluster.generateWorldName(seed);
  57. Dagger 2 - Subcomponent @Module public class GameModule { private

    String seed; public GameModule(String seed) { this.seed = seed; } @Provides @Named("Player") String providePlayer(Cluster cluster) { return cluster.generatePlayerName(); } @Provides @Named("World") String provideWorldName(Cluster cluster) { return cluster.generateWorldName(seed); @Named annotation used to identify different provider with same return type.
  58. Dagger 2 - Subcomponent @Module public class GameModule { private

    String seed; public GameModule(String seed) { this.seed = seed; } @Provides @Named("Player") String providePlayer(Cluster cluster) { return cluster.generatePlayerName(); } @Provides @Named("World") String provideWorldName(Cluster cluster) { return cluster.generateWorldName(seed);
  59. Dagger 2 - Subcomponent public static void main(String[] args) {

    GameComponent subComponent = mainComponent.getGameComponent(new GameModule("42")); String playerName = subComponent.getPlayerName(); String worldName = subComponent.getWorldName(); }
  60. Dagger 2 - Subcomponent public static void main(String[] args) {

    GameComponent subComponent = mainComponent.getGameComponent(new GameModule("42")); String playerName = subComponent.getPlayerName(); String worldName = subComponent.getWorldName(); }
  61. Dagger 2 - Subcomponent public static void main(String[] args) {

    GameComponent subComponent = mainComponent.getGameComponent(new GameModule("42")); String playerName = subComponent.getPlayerName(); String worldName = subComponent.getWorldName(); }
  62. Dagger 2 - There is no Graph There is no

    real ‘graph’ in Dagger 2. Generated code execution path is equivalent to traveling the dependency graph. Constructors are just called in the right order.
  63. Dagger 2 - Behind the Scenes - Provider Impl public

    final class DaggerShinyGamesComponent implements ShinyGamesComponent { private Provider<Processor> provideCPUProvider; private Provider<Memory> provideRAMProvider; private Provider<PowerSupply> providePowerSupplyProvider; private Provider<Motherboard> provideMBProvider; private Provider<Computer> computerProvider; private MembersInjector<MainActivity> mainActivityMembersInjector; // ... @SuppressWarnings("unchecked") private void initialize(final Builder builder) { this.providePowerSupplyProvider = ComputerModule_ProvidePowerSupplyFactory.create(builder.computerModule); this.provideCPUProvider = ComputerModule_ProvideCPUFactory.create(builder.computerModule); this.provideRAMProvider = ComputerModule_ProvideRAMFactory.create(builder.computerModule); this.provideMBProvider = ComputerModule_ProvideMBFactory.create(
  64. Dagger 2 - Behind the Scenes - Provider Impl public

    final class DaggerShinyGamesComponent implements ShinyGamesComponent { private Provider<Processor> provideCPUProvider; private Provider<Memory> provideRAMProvider; private Provider<PowerSupply> providePowerSupplyProvider; private Provider<Motherboard> provideMBProvider; private Provider<Computer> computerProvider; private MembersInjector<MainActivity> mainActivityMembersInjector; // ... @SuppressWarnings("unchecked") private void initialize(final Builder builder) { this.providePowerSupplyProvider = ComputerModule_ProvidePowerSupplyFactory.create(builder.computerModule); this.provideCPUProvider = ComputerModule_ProvideCPUFactory.create(builder.computerModule); this.provideRAMProvider = ComputerModule_ProvideRAMFactory.create(builder.computerModule); this.provideMBProvider = ComputerModule_ProvideMBFactory.create(
  65. Dagger 2 - Behind the Scenes - Provider Impl //

    ... @SuppressWarnings("unchecked") private void initialize(final Builder builder) { this.providePowerSupplyProvider = ComputerModule_ProvidePowerSupplyFactory.create(builder.computerModule); this.provideCPUProvider = ComputerModule_ProvideCPUFactory.create(builder.computerModule); this.provideRAMProvider = ComputerModule_ProvideRAMFactory.create(builder.computerModule); this.provideMBProvider = ComputerModule_ProvideMBFactory.create( builder.computerModule, provideCPUProvider, provideRAMProvider); this.provideComputerProvider = ComputerModule_ProvideComputerFactory.create( builder.computerModule, providePowerSupplyProvider, provideMBProvider); this.provideClusterProvider = DoubleCheck.provider( ComputerModule_ProvideClusterFactory.create( builder.computerModule, provideComputerProvider)); this.mainActivityMembersInjector = MainActivity_MembersInjector.create(provideComputerProvider); }
  66. Dagger 2 - Behind the Scenes - Provider Impl //

    ... @SuppressWarnings("unchecked") private void initialize(final Builder builder) { this.providePowerSupplyProvider = ComputerModule_ProvidePowerSupplyFactory.create(builder.computerModule); this.provideCPUProvider = ComputerModule_ProvideCPUFactory.create(builder.computerModule); this.provideRAMProvider = ComputerModule_ProvideRAMFactory.create(builder.computerModule); this.provideMBProvider = ComputerModule_ProvideMBFactory.create( builder.computerModule, provideCPUProvider, provideRAMProvider); this.provideComputerProvider = ComputerModule_ProvideComputerFactory.create( builder.computerModule, providePowerSupplyProvider, provideMBProvider); this.provideClusterProvider = DoubleCheck.provider( ComputerModule_ProvideClusterFactory.create( builder.computerModule, provideComputerProvider)); this.mainActivityMembersInjector = MainActivity_MembersInjector.create(provideComputerProvider); }
  67. Dagger 2 - Behind the Scenes - Provider Impl public

    final class ComputerModule_ProvideCPUFactory implements Factory<Processor> { private final ComputerModule module; public ComputerModule_ProvideCPUFactory(ComputerModule module) { assert module != null; this.module = module; } @Override public Processor get() { return Preconditions.checkNotNull( module.provideCPU(), "Cannot return null from a non-@Nullable @Provides method"); }
  68. Dagger 2 - Behind the Scenes - Provider Impl public

    final class ComputerModule_ProvideCPUFactory implements Factory<Processor> { private final ComputerModule module; public ComputerModule_ProvideCPUFactory(ComputerModule module) { assert module != null; this.module = module; } @Override public Processor get() { return Preconditions.checkNotNull( module.provideCPU(), "Cannot return null from a non-@Nullable @Provides method"); }
  69. Dagger 2 - Behind the Scenes - Provider Impl public

    final class ComputerModule_ProvideCPUFactory implements Factory<Processor> { private final ComputerModule module; public ComputerModule_ProvideCPUFactory(ComputerModule module) { assert module != null; this.module = module; } @Override public Processor get() { return Preconditions.checkNotNull( module.provideCPU(), "Cannot return null from a non-@Nullable @Provides method"); } This is our @Provides method.
  70. Dagger 2 - Behind the Scenes - Provider Impl //

    ... @SuppressWarnings("unchecked") private void initialize(final Builder builder) { this.providePowerSupplyProvider = ComputerModule_ProvidePowerSupplyFactory.create(builder.computerModule); this.provideCPUProvider = ComputerModule_ProvideCPUFactory.create(builder.computerModule); this.provideRAMProvider = ComputerModule_ProvideRAMFactory.create(builder.computerModule); this.provideMBProvider = ComputerModule_ProvideMBFactory.create( builder.computerModule, provideCPUProvider, provideRAMProvider); this.provideComputerProvider = ComputerModule_ProvideComputerFactory.create( builder.computerModule, providePowerSupplyProvider, provideMBProvider); this.provideClusterProvider = DoubleCheck.provider( ComputerModule_ProvideClusterFactory.create( builder.computerModule, provideComputerProvider)); this.mainActivityMembersInjector = MainActivity_MembersInjector.create(provideComputerProvider); }
  71. Dagger 2 - Behind the Scenes - Provider Impl public

    final class ComputerModule_ProvideMBFactory implements Factory<Motherboard> { private final ComputerModule module; private final Provider<Processor> cpuProvider; private final Provider<Memory> ramProvider; public ComputerModule_ProvideMBFactory( ComputerModule module, Provider<Processor> cpuProvider, Provider<Memory> ramProvider) { // .. } @Override public Motherboard get() { return Preconditions.checkNotNull( module.provideMB(cpuProvider.get(), ramProvider.get()), "Cannot return null from a non-@Nullable @Provides method"); }
  72. Dagger 2 - Behind the Scenes - Provider Impl public

    final class ComputerModule_ProvideMBFactory implements Factory<Motherboard> { private final ComputerModule module; private final Provider<Processor> cpuProvider; private final Provider<Memory> ramProvider; public ComputerModule_ProvideMBFactory( ComputerModule module, Provider<Processor> cpuProvider, Provider<Memory> ramProvider) { // .. } @Override public Motherboard get() { return Preconditions.checkNotNull( module.provideMB(cpuProvider.get(), ramProvider.get()), "Cannot return null from a non-@Nullable @Provides method"); }
  73. Dagger 2 - Behind the Scenes - Provider Impl public

    final class ComputerModule_ProvideMBFactory implements Factory<Motherboard> { private final ComputerModule module; private final Provider<Processor> cpuProvider; private final Provider<Memory> ramProvider; public ComputerModule_ProvideMBFactory( ComputerModule module, Provider<Processor> cpuProvider, Provider<Memory> ramProvider) { // .. } @Override public Motherboard get() { return Preconditions.checkNotNull( module.provideMB(cpuProvider.get(), ramProvider.get()), "Cannot return null from a non-@Nullable @Provides method"); }
  74. Dagger 2 - Behind the Scenes - Provider Impl //

    ... @SuppressWarnings("unchecked") private void initialize(final Builder builder) { this.providePowerSupplyProvider = ComputerModule_ProvidePowerSupplyFactory.create(builder.computerModule); this.provideCPUProvider = ComputerModule_ProvideCPUFactory.create(builder.computerModule); this.provideRAMProvider = ComputerModule_ProvideRAMFactory.create(builder.computerModule); this.provideMBProvider = ComputerModule_ProvideMBFactory.create( builder.computerModule, provideCPUProvider, provideRAMProvider); this.provideComputerProvider = ComputerModule_ProvideComputerFactory.create( builder.computerModule, providePowerSupplyProvider, provideMBProvider); this.provideClusterProvider = DoubleCheck.provider( ComputerModule_ProvideClusterFactory.create( builder.computerModule, provideComputerProvider)); this.mainActivityMembersInjector = MainActivity_MembersInjector.create(provideComputerProvider); }
  75. Dagger 2 - Behind the Scenes - Provider Impl @Singleton

    @Component(modules = ComputerModule.class) public interface ShinyGamesComponent { Computer getPC(); } // DaggerShinyGamesComponent.java @Override public Computer getPC() { return computerProvider.get(); } The component’s getter implementation just calls get() on the provider.
  76. Dagger 2 - Behind the Scenes - Members Injector public

    final class DaggerShinyGamesComponent implements ShinyGamesComponent { private Provider<Processor> provideCPUProvider; private Provider<Memory> provideRAMProvider; private Provider<PowerSupply> providePowerSupplyProvider; private Provider<Motherboard> provideMBProvider; private Provider<Computer> computerProvider; private MembersInjector<MainActivity> mainActivityMembersInjector; // ... @SuppressWarnings("unchecked") private void initialize(final Builder builder) { this.provideCPUProvider = ComputerModule_ProvideCPUFactory.create(builder.computerModule); this.provideRAMProvider = ComputerModule_ProvideRAMFactory.create(builder.computerModule); this.providePowerSupplyProvider = ComputerModule_ProvidePowerSupplyFactory.create(builder.computerModule);
  77. Dagger 2 - Behind the Scenes - Members Injector //

    ... @SuppressWarnings("unchecked") private void initialize(final Builder builder) { this.providePowerSupplyProvider = ComputerModule_ProvidePowerSupplyFactory.create(builder.computerModule); this.provideCPUProvider = ComputerModule_ProvideCPUFactory.create(builder.computerModule); this.provideRAMProvider = ComputerModule_ProvideRAMFactory.create(builder.computerModule); this.provideMBProvider = ComputerModule_ProvideMBFactory.create( builder.computerModule, provideCPUProvider, provideRAMProvider); this.provideComputerProvider = ComputerModule_ProvideComputerFactory.create( builder.computerModule, providePowerSupplyProvider, provideMBProvider); this.provideClusterProvider = DoubleCheck.provider( ComputerModule_ProvideClusterFactory.create( builder.computerModule, provideComputerProvider)); this.mainActivityMembersInjector = MainActivity_MembersInjector.create(provideComputerProvider); }
  78. Dagger 2 - Behind the Scenes - Members Injector public

    final class MainActivity_MembersInjector implements MembersInjector<MainActivity> { private final Provider<Computer> computerProvider; public MainActivity_MembersInjector(Provider<Computer> computerProvider) { assert computerProvider != null; this.computerProvider = computerProvider; } @Override public void injectMembers(MainActivity instance) { if (instance == null) { throw new NullPointerException("Cannot inject members into a null reference"); } instance.computer = computerProvider.get(); }
  79. Dagger 2 - Behind the Scenes - Members Injector public

    final class MainActivity_MembersInjector implements MembersInjector<MainActivity> { private final Provider<Computer> computerProvider; public MainActivity_MembersInjector(Provider<Computer> computerProvider) { assert computerProvider != null; this.computerProvider = computerProvider; } @Override public void injectMembers(MainActivity instance) { if (instance == null) { throw new NullPointerException("Cannot inject members into a null reference"); } instance.computer = computerProvider.get(); }
  80. Dagger 2 - Behind the Scenes - Members Injector public

    final class MainActivity_MembersInjector implements MembersInjector<MainActivity> { private final Provider<Computer> computerProvider; public MainActivity_MembersInjector(Provider<Computer> computerProvider) { assert computerProvider != null; this.computerProvider = computerProvider; } @Override public void injectMembers(MainActivity instance) { if (instance == null) { throw new NullPointerException("Cannot inject members into a null reference"); } instance.computer = computerProvider.get(); }
  81. Dagger 2 - Behind the Scenes - Members Injector @Singleton

    @Component(modules = ComputerModule.class) public interface ShinyGamesComponent { void inject(MainActivity mainActivity); } // DaggerShinyGamesComponent.java @Override public void inject(MainActivity mainActivity) { mainActivityMembersInjector.injectMembers(mainActivity); } The component’s inject implementation just sets each field using Providers.
  82. Dagger 2 - Scoped Provider // ... @SuppressWarnings("unchecked") private void

    initialize(final Builder builder) { this.providePowerSupplyProvider = ComputerModule_ProvidePowerSupplyFactory.create(builder.computerModule); this.provideCPUProvider = ComputerModule_ProvideCPUFactory.create(builder.computerModule); this.provideRAMProvider = ComputerModule_ProvideRAMFactory.create(builder.computerModule); this.provideMBProvider = ComputerModule_ProvideMBFactory.create( builder.computerModule, provideCPUProvider, provideRAMProvider); this.provideComputerProvider = ComputerModule_ProvideComputerFactory.create( builder.computerModule, providePowerSupplyProvider, provideMBProvider); this.provideClusterProvider = DoubleCheck.provider( ComputerModule_ProvideClusterFactory.create( builder.computerModule, provideComputerProvider)); this.mainActivityMembersInjector = MainActivity_MembersInjector.create(provideComputerProvider); }
  83. Dagger 2 - Scoped Provider // ... @SuppressWarnings("unchecked") private void

    initialize(final Builder builder) { this.providePowerSupplyProvider = ComputerModule_ProvidePowerSupplyFactory.create(builder.computerModule); this.provideCPUProvider = ComputerModule_ProvideCPUFactory.create(builder.computerModule); this.provideRAMProvider = ComputerModule_ProvideRAMFactory.create(builder.computerModule); this.provideMBProvider = ComputerModule_ProvideMBFactory.create( builder.computerModule, provideCPUProvider, provideRAMProvider); this.provideComputerProvider = ComputerModule_ProvideComputerFactory.create( builder.computerModule, providePowerSupplyProvider, provideMBProvider); this.provideClusterProvider = DoubleCheck.provider( ComputerModule_ProvideClusterFactory.create( builder.computerModule, provideComputerProvider)); this.mainActivityMembersInjector = MainActivity_MembersInjector.create(provideComputerProvider); } Scoped dependencies are wrapped around with a special Provider implementation
  84. Dagger 2 - Scoped Provider public final class DoubleCheck<T> implements

    Provider<T>, Lazy<T> { private static final Object UNINITIALIZED = new Object(); private volatile Provider<T> provider; private volatile Object instance = UNINITIALIZED; private DoubleCheck(Provider<T> provider) { assert provider != null; this.provider = provider; } @SuppressWarnings("unchecked") @Override public T get() { Object result = instance; if (result == UNINITIALIZED) { synchronized (this) { result = instance; if (result == UNINITIALIZED) {
  85. Dagger 2 - Scoped Provider public final class DoubleCheck<T> implements

    Provider<T>, Lazy<T> { private static final Object UNINITIALIZED = new Object(); private volatile Provider<T> provider; private volatile Object instance = UNINITIALIZED; private DoubleCheck(Provider<T> provider) { assert provider != null; this.provider = provider; } @SuppressWarnings("unchecked") @Override public T get() { Object result = instance; if (result == UNINITIALIZED) { synchronized (this) { result = instance; if (result == UNINITIALIZED) {
  86. Dagger 2 - Scoped Provider public T get() { Object

    result = instance; if (result == UNINITIALIZED) { synchronized (this) { result = instance; if (result == UNINITIALIZED) { result = provider.get(); Object currentInstance = instance; if (currentInstance != UNINITIALIZED && currentInstance != result) { throw new IllegalStateException("Scoped provider was invoked recursively returning " + "different results: " + currentInstance + " & " + result); } instance = result; provider = null; } } } return (T) result; }
  87. Dagger 2 - Scoped Provider public T get() { Object

    result = instance; if (result == UNINITIALIZED) { // ... result = provider.get(); // ... } return (T) result; } A sophisticated null check.
  88. We had some bad habits... • Abused the singleton pattern.

    • Global getters and state holder. • No dependency management. These patterns organically grew into the app as time passed.
  89. We had some bad habits... • Abused the singleton pattern.

    • Global getters and state holder. • No dependency management. public static void startStation(String stationId) { Radio radio = AppGlobals.getInstance().getRadio(); Player player = radio.getPlayer(); Station newStation = StationManager.getInstance().getNewStation(stationId); player.startStation(newStation); AppGlobals.getInstance().setStationPlaying(stationId); }
  90. We had some bad habits... public static void startStation(String stationId)

    { Radio radio = AppGlobals.getInstance().getRadio(); Player player = radio.getPlayer(); Station newStation = StationManager.getInstance().getNewStation(stationId); player.startStation(newStation); AppGlobals.getInstance().setStationPlaying(stationId); } These weren’t interfaces!
  91. We had some bad habits... Someone had to ‘initialize and

    set Radio’ first or else this would return null. public static void startStation(String stationId) { Radio radio = AppGlobals.getInstance().getRadio(); Player player = radio.getPlayer(); Station newStation = StationManager.getInstance().getNewStation(stationId); player.startStation(newStation); AppGlobals.getInstance().setStationPlaying(stationId); }
  92. Bad habits caused problems Long living objects. Everything had to

    be early initialized, long startup times. Very hard to figure out what depended on what. Very hard to test, no trivial way of providing mocks.
  93. Divide and Conquer Started with simple classes with little to

    no dependencies. /** * An util class that will pause music playback when the user has been listening * at volume level zero. */ public class ZeroVolumeDetector {
  94. Divide and Conquer Refactoring usages of ‘AppGlobals’ to use member

    fields. /** * An util class that will pause music playback when the user has been listening * at volume level zero. */ public class ZeroVolumeDetector { private void onDetect() { AppGlobals.getRadio().getPlayer().pause(); StatsCollector.getInstance().registerZeroVolume(); } }
  95. Divide and Conquer Refactoring usages of ‘AppGlobals’ to use member

    fields. /** * An util class that will pause music playback when the user has been listening * at volume level zero. */ public class ZeroVolumeDetector { private final Player player; private final StatsCollector statsCollector; private void onDetect() { player.pause(); statsCollector.registerZeroVolume(); } }
  96. Divide and Conquer Refactor constructor signature to take dependencies. /**

    * An util class that will pause music playback when the user has been listening * at volume level zero. */ public class ZeroVolumeDetector { private final Player player; private final StatsCollector statsCollector; public ZeroVolumeDetector(Player player, StatsCollector statsCollector) { this.player = player; this.statsCollector = statsCollector; } private void onDetect() { player.pause();
  97. Divide and Conquer Add @Provides method in appropriate Module. Temporarily

    refactor all usages of the class to grab it the instance from the component. Devs would take one class at a time.
  98. Divide and Conquer Tackled global objects early, slowly replacing the

    singleton pattern with scoped providers. Later more complicated classes, field injection in Android components and runtime swappable objects. As work progressed some circular dependencies surfaced, those were hard. Developed patterns and rules and involved more devs.
  99. Use construction injection whenever possible It's plain old Java, no

    ties with any dependency injection framework. private final Bus bus; private final Player player; private final StatsCollector statsCollector; private final AudioManager audioManager; public ZeroVolumeDetector(Bus bus, Player player, StatsCollector statsCollector, AudioManager audioManager) { this.bus = bus; this.player = player; this.statsCollector = statsCollector; this.audioManager = audioManager; }
  100. Use construction injection whenever possible private final Bus bus; private

    final Player player; private final StatsCollector statsCollector; private final AudioManager audioManager; public ZeroVolumeDetector(Bus bus, Player player, StatsCollector statsCollector, AudioManager audioManager) { this.bus = bus; this.player = player; this.statsCollector = statsCollector; this.audioManager = audioManager; } Immutable dependencies.
  101. Use construction injection whenever possible private final Bus bus; private

    final Player player; private final StatsCollector statsCollector; private final AudioManager audioManager; public ZeroVolumeDetector(Bus bus, Player player, StatsCollector statsCollector, AudioManager audioManager) { this.bus = bus; this.player = player; this.statsCollector = statsCollector; this.audioManager = audioManager; } Dependencies are clearly defined.
  102. Use construction injection whenever possible private final Bus bus; private

    final Player player; private final StatsCollector statsCollector; private final AudioManager audioManager; public ZeroVolumeDetector(Bus bus, Player player, StatsCollector statsCollector, AudioManager audioManager) { this.bus = bus; this.player = player; this.statsCollector = statsCollector; this.audioManager = audioManager; } Dependencies should be interfaces.
  103. Use construction injection whenever possible Dependencies changes causes signature change

    and compile failures. Fail fast to fix fast. In test world construct test object with mocks. MockitoAnnotations helps eliminating boilerplate.
  104. Use field injection on system initialized objects Various Android building

    blocks are initialized by the system and require a public no-args constructor. • Activity - onCreate() • Fragment - onCreate() • Service - onCreate() • ContentProvider - onCreate() (Watch out! This can be called before Application#onCreate) • BroadcastReceiver - onReceive() • View - onFinishInflate()
  105. Resolving a dependency cycle Dagger doesn’t allow circular dependencies. Player

    AudioFocus Manager class Player { @Inject Player(AudioFocusManager audioFocusManager) { this.audioFocusManager = audioFocusManager; } } class AudioFocusManager { @Inject AudioFocusManager(Player player) { this.player = player; } }
  106. Resolving a dependency cycle AudioFocusManager needs Player to pause music

    when another apps grabs focus. Player needs AudioFocusManager to request focus when playback starts. Player AudioFocus Manager
  107. Resolving a dependency cycle Break the cycle with a ‘3rd’

    object of unrelated or no dependencies. AudioFocusManager still depends on Player to pause music when focus is lost. Now Player inform its state to an object that is able to notify listeners. AudioFocusManager is registered and when music starts it requests focus. Player AudioFocus Manager PlayerState
  108. Resolving a dependency cycle Do a lazy injection. Player AudioFocus

    Manager public class Player { private final Provider<AudioFocusManager> audioFocusManager; @Inject Player(Provider<AudioFocusManager> audioFocusManager) { this.audioFocusManager = audioFocusManager; } void startStation() { audioFocusManager.get().requestAudioFocus(); } }
  109. Resolving a dependency cycle Do a lazy injection. Player AudioFocus

    Manager public class Player { private final Provider<AudioFocusManager> audioFocusManager; @Inject Player(Provider<AudioFocusManager> audioFocusManager) { this.audioFocusManager = audioFocusManager; } void startStation() { audioFocusManager.get().requestAudioFocus(); } } Injection is delayed until usage.
  110. Factory Pattern + Providers for runtime constructions For objects that

    depend on singletons and are constructed with runtime values, we combine the Factory Pattern with Providers. interface TrackFactory { Track create(String musicId); }
  111. Factory Pattern + Providers for runtime constructions public class TrackFactoryImpl

    implements TrackFactory { private final Provider<ExoPlayer> exoPlayerProvider; public TrackFactoryImpl(Provider<ExoPlayer> exoPlayerProvider) { this.exoPlayerProvider = exoPlayerProvider; } @Override public Track create(String musicId) { return new ExoPlayerTrack(musicId, exoPlayerProvider.get()); } }
  112. Factory Pattern + Providers for runtime constructions public class TrackFactoryImpl

    implements TrackFactory { private final Provider<ExoPlayer> exoPlayerProvider; public TrackFactoryImpl(Provider<ExoPlayer> exoPlayerProvider) { this.exoPlayerProvider = exoPlayerProvider; } @Override public Track create(String musicId) { return new ExoPlayerTrack(musicId, exoPlayerProvider.get()); } }
  113. Factory Pattern + Providers for runtime constructions public class TrackFactoryImpl

    implements TrackFactory { private final Provider<ExoPlayer> exoPlayerProvider; public TrackFactoryImpl(Provider<ExoPlayer> exoPlayerProvider) { this.exoPlayerProvider = exoPlayerProvider; } @Override public Track create(String musicId) { return new ExoPlayerTrack(musicId, exoPlayerProvider.get()); } }
  114. Factory Pattern + Providers for runtime constructions public class TrackFactoryImpl

    implements TrackFactory { private final Provider<ExoPlayer> exoPlayerProvider; public TrackFactoryImpl(Provider<ExoPlayer> exoPlayerProvider) { this.exoPlayerProvider = exoPlayerProvider; } @Override public Track create(String musicId) { return new ExoPlayerTrack(musicId, exoPlayerProvider.get()); } } @Provides @Singleton TrackFactory provideTrackFactory(Provider<ExoPlayer> exoPlayerProvider) { return new TrackFactoryImpl(exoPlayerProvider); }
  115. Subcomponent for runtime constructions For objects that depend on singletons

    and are constructed with runtime values, we can alternatively use Subcomponents. @Singleton @Component(modules = RadioModule.class) public interface RadioComponent { TrackSubcomponent getTrackComponent(TrackModule module); } @Subcomponent(modules = TrackModule.class) public interface TrackSubcomponent { Track getTrack(); }
  116. Subcomponent for runtime constructions @Module public class RadioModule { @Provides

    @Singleton ExoPlayer provideExoPlayer() { return ExoPlayer.Factory.newInstance(2); } } @Module public class TrackModule { private final String musicId; public TrackModule(String musicId) { this.musicId = musicId; } @Provides Track provideTrack(ExoPlayer exoPlayer) { return new ExoPlayerTrack(musicId, exoPlayer); } }
  117. Subcomponent for runtime constructions public static void main(String[] args) {

    RadioComponent radioComponent = DaggerRadioComponent.create(); TrackModule trackModule = new TrackModule("1234"); TrackSubcomponent trackSubcomponent = radioComponent .getTrackComponent(trackModule); Track track = trackSubcomponent.getTrack(); }
  118. Dagger + Libraries For libraries we expect a component that

    uses library defined modules. @Singleton @Component(modules = { AppModule.class, // Radio library modules RadioModule.class, // Auto library modules AutoModule.class }) abstract class AppComponent implements RadioComponent, AutoComponent { abstract Application getApp(); }
  119. Dagger + Libraries public interface RadioComponent { Player getPlayer(); }

    @Module public class RadioModule { @Provides @Singleton Player providePlayer() { return new PlayerImpl(); } } public interface AutoComponent { PandoraLink getPandoraLink(); } @Module public class AutoModule { @Provides @Singleton PandoraLink provide(Player player) { return new PandoraLink(player); } }
  120. Our Patterns - Summary 1. Use construction injection above all.

    2. Use field injection on system initialized objects. a. Activity, Fragment, Service, BroadcastReceiver, ContentProvider, View 3. Resolve circular dependencies by creating a 3rd object or by lazy injection. 4. Use the Factory pattern along with Providers<> for runtime constructed objects. 5. Libraries expect a Component and Main Component should implement them.
  121. Extra • Prefer @Provides methods instead of annotating constructors with

    @Inject. ◦ Easier to keep track what Dagger provides and avoids @Provides vs @Inject precedence. • Avoid using @Module(includes = { … }) ◦ If all modules are in the components, that a single place to look for them. • Field inject concrete classes along the inheritance line. ◦ Field injection will populate fields of the class and its super classes, but not its child. i.e. don’t expect the base class to inject your dependencies.
  122. Testing - Construction injection Small ‘unit’ tests are easy with

    construction injection. 1. Create mocks dependencies 2. Set dependencies expectations 3. Build object to test 4. Do stuff with test object 5. Verify expectations
  123. Testing - Construction injection - MockitoAnnotations @Mock private Processor mockProcessor;

    @Mock private Memory mockMemory; @InjectMocks private ATX motherboard; @Before public void setup() { MockitoAnnotations.initMocks(this); } @Test public void test_getCPU() { assertEquals(mockProcessor, motherboard.getCPU()); assertEquals(mockMemory, motherboard.getRAM()); } public class ATX implements Motherboard { private final Processor cpu; private final Memory ram; public ATX(Processor cpu, Memory ram) { this.cpu = cpu; this.ram = ram; } @Override public Processor getCPU() { return cpu; } @Override public Memory getRAM() { return ram; } }
  124. Testing - Construction injection - MockitoAnnotations @Mock private Processor mockProcessor;

    @Mock private Memory mockMemory; @InjectMocks private ATX motherboard; @Before public void setup() { MockitoAnnotations.initMocks(this); } @Test public void test_getCPU() { assertEquals(mockProcessor, motherboard.getCPU()); assertEquals(mockMemory, motherboard.getRAM()); } public class ATX implements Motherboard { private final Processor cpu; private final Memory ram; public ATX(Processor cpu, Memory ram) { this.cpu = cpu; this.ram = ram; } @Override public Processor getCPU() { return cpu; } @Override public Memory getRAM() { return ram; } }
  125. Testing - Construction injection - MockitoAnnotations @Mock private Processor mockProcessor;

    @Mock private Memory mockMemory; @InjectMocks private ATX motherboard; @Before public void setup() { MockitoAnnotations.initMocks(this); } @Test public void test_getCPU() { assertEquals(mockProcessor, motherboard.getCPU()); assertEquals(mockMemory, motherboard.getRAM()); } public class ATX implements Motherboard { private final Processor cpu; private final Memory ram; public ATX(Processor cpu, Memory ram) { this.cpu = cpu; this.ram = ram; } @Override public Processor getCPU() { return cpu; } @Override public Memory getRAM() { return ram; } }
  126. Testing - Construction injection - MockitoAnnotations @Mock private Processor mockProcessor;

    @Mock private Memory mockMemory; @InjectMocks private ATX motherboard; @Before public void setup() { MockitoAnnotations.initMocks(this); } @Test public void test_getCPU() { assertEquals(mockProcessor, motherboard.getCPU()); assertEquals(mockMemory, motherboard.getRAM()); } public class ATX implements Motherboard { private final Processor cpu; private final Memory ram; public ATX(Processor cpu, Memory ram) { this.cpu = cpu; this.ram = ram; } @Override public Processor getCPU() { return cpu; } @Override public Memory getRAM() { return ram; } }
  127. Testing - Construction injection - MockitoAnnotations @Mock private Processor mockProcessor;

    @Mock private Memory mockMemory; @InjectMocks private ATX motherboard; @Before public void setup() { MockitoAnnotations.initMocks(this); } @Test public void test_getCPU() { assertEquals(mockProcessor, motherboard.getCPU()); assertEquals(mockMemory, motherboard.getRAM()); } public class ATX implements Motherboard { private final Processor cpu; private final Memory ram; public ATX(Processor cpu, Memory ram) { this.cpu = cpu; this.ram = ram; } @Override public Processor getCPU() { return cpu; } @Override public Memory getRAM() { return ram; } }
  128. Testing - Field injection Method 1 - Mock component, manually

    set fields. @Test public void test_onCreate_v1() { Player mockPlayer = Mockito.mock(Player.class); MainActivity.appComponent = Mockito.mock(AppComponent.class); ActivityController<MainActivity> controller = Robolectric.buildActivity(MainActivity.class); MainActivity activity = controller.get(); activity.player = mockPlayer; controller.create(); assertEquals(activity.player, mockPlayer); Mockito.verify(mockPlayer).startStation(); }
  129. Testing - Field injection Method 1 - Mock component, manually

    set fields. @Test public void test_onCreate_v1() { Player mockPlayer = Mockito.mock(Player.class); MainActivity.appComponent = Mockito.mock(AppComponent.class); ActivityController<MainActivity> controller = Robolectric.buildActivity(MainActivity.class); MainActivity activity = controller.get(); activity.player = mockPlayer; controller.create(); assertEquals(activity.player, mockPlayer); Mockito.verify(mockPlayer).startStation(); }
  130. Testing - Field injection Method 1 - Mock component, manually

    set fields. @Test public void test_onCreate_v1() { Player mockPlayer = Mockito.mock(Player.class); MainActivity.appComponent = Mockito.mock(AppComponent.class); ActivityController<MainActivity> controller = Robolectric.buildActivity(MainActivity.class); MainActivity activity = controller.get(); activity.player = mockPlayer; controller.create(); assertEquals(activity.player, mockPlayer); Mockito.verify(mockPlayer).startStation(); }
  131. Testing - Field injection Method 1 - Mock component, manually

    set fields. @Test public void test_onCreate_v1() { Player mockPlayer = Mockito.mock(Player.class); MainActivity.appComponent = Mockito.mock(AppComponent.class); ActivityController<MainActivity> controller = Robolectric.buildActivity(MainActivity.class); MainActivity activity = controller.get(); activity.player = mockPlayer; controller.create(); assertEquals(activity.player, mockPlayer); Mockito.verify(mockPlayer).startStation(); }
  132. Testing - Field injection Method 1 - Mock component, manually

    set fields. @Test public void test_onCreate_v1() { Player mockPlayer = Mockito.mock(Player.class); MainActivity.appComponent = Mockito.mock(AppComponent.class); ActivityController<MainActivity> controller = Robolectric.buildActivity(MainActivity.class); MainActivity activity = controller.get(); activity.player = mockPlayer; controller.create(); assertEquals(activity.player, mockPlayer); Mockito.verify(mockPlayer).startStation(); }
  133. Testing - Field injection Method 2 - Use Mocking Framework

    @Test public void test_onCreate_v2() { final Player mockPlayer = Mockito.mock(Player.class); AppComponent mockAppComponent = Mockito.mock(AppComponent.class); Mockito.doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { MainActivity activity = (MainActivity) invocation.getArguments()[0]; activity.player = mockPlayer; return null; } }).when(mockAppComponent).inject(Mockito.any(MainActivity.class)); MainActivity.appComponent = mockAppComponent; MainActivity activity = Robolectric.buildActivity(MainActivity.class).create().get(); assertEquals(activity.player, mockPlayer); Mockito.verify(mockPlayer).startStation(); }
  134. Testing - Field injection Method 2 - Use Mocking Framework

    @Test public void test_onCreate_v2() { final Player mockPlayer = Mockito.mock(Player.class); AppComponent mockAppComponent = Mockito.mock(AppComponent.class); Mockito.doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { MainActivity activity = (MainActivity) invocation.getArguments()[0]; activity.player = mockPlayer; return null; } }).when(mockAppComponent).inject(Mockito.any(MainActivity.class)); MainActivity.appComponent = mockAppComponent; MainActivity activity = Robolectric.buildActivity(MainActivity.class).create().get(); assertEquals(activity.player, mockPlayer); Mockito.verify(mockPlayer).startStation(); }
  135. Testing - Field injection Method 2 - Use Mocking Framework

    @Test public void test_onCreate_v2() { final Player mockPlayer = Mockito.mock(Player.class); AppComponent mockAppComponent = Mockito.mock(AppComponent.class); Mockito.doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { MainActivity activity = (MainActivity) invocation.getArguments()[0]; activity.player = mockPlayer; return null; } }).when(mockAppComponent).inject(Mockito.any(MainActivity.class)); MainActivity.appComponent = mockAppComponent; MainActivity activity = Robolectric.buildActivity(MainActivity.class).create().get(); assertEquals(activity.player, mockPlayer); Mockito.verify(mockPlayer).startStation(); }
  136. Testing - Field injection Method 2 - Use Mocking Framework

    @Test public void test_onCreate_v2() { final Player mockPlayer = Mockito.mock(Player.class); AppComponent mockAppComponent = Mockito.mock(AppComponent.class); Mockito.doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { MainActivity activity = (MainActivity) invocation.getArguments()[0]; activity.player = mockPlayer; return null; } }).when(mockAppComponent).inject(Mockito.any(MainActivity.class)); MainActivity.appComponent = mockAppComponent; MainActivity activity = Robolectric.buildActivity(MainActivity.class).create().get(); assertEquals(activity.player, mockPlayer); Mockito.verify(mockPlayer).startStation(); }
  137. Testing - Field injection Method 3 - Override Modules @Test

    public void test_onCreate_v3() { final Player mockPlayer = Mockito.mock(Player.class); MainActivity.appComponent = DaggerAppComponent.builder() .radioModule(new RadioModule() { @Override protected Player providePlayer() { return mockPlayer; } }).build(); MainActivity activity = Robolectric.buildActivity(MainActivity.class).create().get(); assertEquals(activity.player, mockPlayer); Mockito.verify(mockPlayer).startStation(); }
  138. Testing - Field injection Method 3 - Override Modules @Test

    public void test_onCreate_v3() { final Player mockPlayer = Mockito.mock(Player.class); MainActivity.appComponent = DaggerAppComponent.builder() .radioModule(new RadioModule() { @Override protected Player providePlayer() { return mockPlayer; } }).build(); MainActivity activity = Robolectric.buildActivity(MainActivity.class).create().get(); assertEquals(activity.player, mockPlayer); Mockito.verify(mockPlayer).startStation(); }
  139. Testing - Field injection Method 3 - Override Modules @Test

    public void test_onCreate_v3() { final Player mockPlayer = Mockito.mock(Player.class); MainActivity.appComponent = DaggerAppComponent.builder() .radioModule(new RadioModule() { @Override protected Player providePlayer() { return mockPlayer; } }).build(); MainActivity activity = Robolectric.buildActivity(MainActivity.class).create().get(); assertEquals(activity.player, mockPlayer); Mockito.verify(mockPlayer).startStation(); }
  140. Testing - Field injection Method 3 - Override Modules @Test

    public void test_onCreate_v3() { final Player mockPlayer = Mockito.mock(Player.class); MainActivity.appComponent = DaggerAppComponent.builder() .radioModule(new RadioModule() { @Override protected Player providePlayer() { return mockPlayer; } }).build(); MainActivity activity = Robolectric.buildActivity(MainActivity.class).create().get(); assertEquals(activity.player, mockPlayer); Mockito.verify(mockPlayer).startStation(); }
  141. Testing with Provider<> @Test public void test_stationStart() { AudioFocusManager mockAudioFocusManager

    = Mockito.mock(AudioFocusManager.class); Player player = new Player(InstanceFactory.create(mockAudioFocusManager)); player.startStation(); Mockito.verify(mockAudioFocusManager).requestAudioFocus(); }
  142. Testing with Provider<> @Test public void test_stationStart() { AudioFocusManager mockAudioFocusManager

    = Mockito.mock(AudioFocusManager.class); Player player = new Player(InstanceFactory.create(mockAudioFocusManager)); player.startStation(); Mockito.verify(mockAudioFocusManager).requestAudioFocus(); }
  143. Testing - Instrumentation Dedicated Espresso Flavor. Components and Modules are

    overridden. Mock api calls. OkHttp - MockWebServer