Dependency Injection Made Simple (360|AnDev)

Dependency Injection Made Simple (360|AnDev)

Version of talk given at 360|AnDev.

D225ebf0faa666ac7655cc7e4689283c?s=128

Daniel Lew

July 29, 2016
Tweet

Transcript

  1. Dependency injection made simple Dan Lew

  2. What is a dependency?

  3. A depends on B

  4. • Data provider depends on database • Image Loader depends

    on HTTP • REST depends on HTTP • REST depends on deserializer • Login screen depends on all the above
  5. What is dependency injection?

  6. "Dependency Injection" is a 25-dollar term for a 5-cent concept.

    http://www.jamesshore.com/Blog/Dependency-Injection-Demystified.html ~James Shore
  7. public class Example {
 private final Dependency dependency;
 
 public

    Example() {
 dependency = new Dependency();
 }
 } VS public class Example {
 private final Dependency dependency;
 
 public Example(Dependency dependency) {
 this.dependency = dependency;
 }
 }
  8. public class Example {
 private final Dependency dependency;
 
 public

    Example() {
 dependency = new Dependency();
 }
 } VS public class Example {
 private final Dependency dependency;
 
 public Example(Dependency dependency) {
 this.dependency = dependency;
 }
 }
  9. • Constructor injection public class Example {
 private final Dependency

    dependency;
 
 public Example(Dependency dependency) {
 this.dependency = dependency;
 }
 } • Method injection public class Example {
 private Dependency dependency;
 
 public Example() { }
 
 public void setDependency(Dependency dependency) {
 this.dependency = dependency;
 }
 }
  10. View(Context context)

  11. None
  12. But Why? • Share dependencies • Configure dependencies externally

  13. Context wrappedContext = new ContextThemeWrapper(context, R.style.MyTheme); 
 View view =

    new View(wrappedContext);
  14. Dependency inversion principle • High-level modules should not depend on

    low-level modules. Both should depend on abstractions. • Abstractions should not depend on details. Details should depend on abstractions.
  15. A depends on B

  16. A is coupled to B

  17. None
  18. None
  19. void copy() {
 char c;
 while ((c = readKeyboard()) !=

    -1) {
 writePrinter(c);
 }
 }
  20. What if… • …You want to read from disk? enum

    InputDevice {
 KEYBOARD, DISK
 }
 
 void copy(InputDevice input) {
 char c;
 while (true) {
 if (input == InputDevice.KEYBOARD) {
 c = readKeyboard();
 } else if (input == InputDevice.DISK) {
 c = readDisk();
 } else {
 throw new IllegalArgumentException("Whoops!");
 }
 
 if (c == -1) {
 return;
 }
 
 writePrinter(c);
 }
 }
  21. What if… • …You want to write to the monitor?

    enum OutputDevice {
 PRINTER, MONITOR
 }
 
 void copy(OutputDevice outputDevice) {
 char c;
 while ((c = readKeyboard()) != -1) {
 if (outputDevice == OutputDevice.PRINTER) {
 writePrinter(c);
 } else if (outputDevice == OutputDevice.MONITOR) {
 writeMonitor(c);
 } else {
 throw new IllegalArgumentException("Whoops again!");
 }
 }
 }
  22. • What do we test in the read / write

    modules? • How do we test copy?
  23. Dependency inversion to the rescue

  24. None
  25. interface Reader {
 char read();
 }
 
 interface Writer {


    void write(char c);
 }
 
 void copy(Reader reader, Writer writer) {
 char c;
 while ((c = reader.read()) != -1) {
 writer.write(c);
 }
 }
  26. But Why? • Share dependencies • Configure dependencies externally

  27. None
  28. But Why? • Share dependencies • Configure dependencies externally •

    Clean separation of modules
  29. None
  30. But Why? • Share dependencies • Configure dependencies externally •

    Clean separation of modules • Easy testing
  31. None
  32. @Test
 public void testCopy() {
 TestReader reader = new TestReader("Hello,

    world!");
 TestWriter writer = new TestWriter();
 copy(reader, writer);
 assertEquals("Hello, world!", writer.getWritten());
 }
  33. But Why? • Share dependencies • Configure dependencies externally •

    Clean separation of modules • Easy testing • Easy debugging
  34. …Why not? • More verbose • More complex

  35. None
  36. Framework Advantages • Handles injection busywork • Handles logic (e.g.

    singletons, lazy loading) • Unified system for dependencies
  37. Frameworks • Dagger 1 • Dagger 2 • Guice •

    Spring • PicoContainer • …And on and on and on…
  38. Dagger Directed Acyclic Graph

  39. Object Graph

  40. Greeter

  41. // Greeter.java public class Greeter {
 @Inject public Greeter() {

    }
 
 public void helloWorld() {
 System.out.println("Hello, world!");
 }
 } // MyModule.java @Module(injects = Greeter.class)
 public final class MyModule { } // Retrieving Greeter ObjectGraph objectGraph = ObjectGraph.create(new MyModule()); Greeter greeter = objectGraph.get(Greeter.class); greeter.helloWorld();
  42. // Greeter.java public class Greeter {
 @Inject public Greeter() {

    }
 
 public void helloWorld() {
 System.out.println("Hello, world!");
 }
 } // MyModule.java @Module(injects = Greeter.class)
 public final class MyModule { } // Retrieving Greeter ObjectGraph objectGraph = ObjectGraph.create(new MyModule()); Greeter greeter = objectGraph.get(Greeter.class); greeter.helloWorld();
  43. // Greeter.java public class Greeter {
 @Inject public Greeter() {

    }
 
 public void helloWorld() {
 System.out.println("Hello, world!");
 }
 } // MyModule.java @Module(injects = Greeter.class)
 public final class MyModule { } // Retrieving Greeter ObjectGraph objectGraph = ObjectGraph.create(new MyModule()); Greeter greeter = objectGraph.get(Greeter.class); greeter.helloWorld();
  44. // Greeter.java public class Greeter {
 @Inject public Greeter() {

    }
 
 public void helloWorld() {
 System.out.println("Hello, world!");
 }
 } // MyModule.java @Module(injects = Greeter.class)
 public final class MyModule { } // Retrieving Greeter ObjectGraph objectGraph = ObjectGraph.create(new MyModule()); Greeter greeter = objectGraph.get(Greeter.class); greeter.helloWorld();
  45. // Greeter.java public class Greeter {
 @Inject public Greeter() {

    }
 
 public void helloWorld() {
 System.out.println("Hello, world!");
 }
 } // MyModule.java @Module(injects = Greeter.class)
 public final class MyModule { } // Retrieving Greeter ObjectGraph objectGraph = ObjectGraph.create(new MyModule()); Greeter greeter = objectGraph.get(Greeter.class); greeter.helloWorld();
  46. Greeter Text

  47. // Text.java
 public class Text {
 @Inject public Text() {

    }
 
 public String getText() {
 return "Hello, world!";
 }
 }
 // Greeter.java
 public class Greeter {
 private final Text text;
 
 @Inject public Greeter(Text text) {
 this.text = text;
 }
 
 public void sayText() {
 System.out.println(text.getText());
 }
 }
  48. // Text.java
 public class Text {
 @Inject public Text() {

    }
 
 public String getText() {
 return "Hello, world!";
 }
 }
 // Greeter.java
 public class Greeter {
 private final Text text;
 
 @Inject public Greeter(Text text) {
 this.text = text;
 }
 
 public void sayText() {
 System.out.println(text.getText());
 }
 }
  49. // Text.java
 public class Text {
 @Inject public Text() {

    }
 
 public String getText() {
 return "Hello, world!";
 }
 }
 // Greeter.java
 public class Greeter {
 private final Text text;
 
 @Inject public Greeter(Text text) {
 this.text = text;
 }
 
 public void sayText() {
 System.out.println(text.getText());
 }
 }
  50. // Text.java
 public class Text {
 @Inject public Text() {

    }
 
 public String getText() {
 return "Hello, world!";
 }
 }
 // Greeter.java
 public class Greeter {
 private final Text text;
 
 @Inject public Greeter(Text text) {
 this.text = text;
 }
 
 public void sayText() {
 System.out.println(text.getText());
 }
 }
  51. @Inject
 public ManyDependencies(
 Dep1 bulbasaur,
 Dep2 charmander,
 Dep3 squirtle,
 Dep4

    caterpie,
 Dep5 pikachu, Dep6 slowpoke
 )
  52. // Greeter.java
 public class Greeter {
 @Inject Text text;
 


    public void sayText() {
 System.out.println(text.getText());
 }
 }
  53. // Greeter.java
 public class Greeter {
 @Inject Text text;
 


    public void sayText() {
 System.out.println(text.getText());
 }
 }
  54. public class ManyDependencies {
 @Inject Dep1 eevee;
 @Inject Dep2 ditto;


    @Inject Dep3 snorlax;
 @Inject Dep4 articuno;
 @Inject Dep5 dragonite;
 @Inject Dep6 mew;
 }
  55. Providers • Explicitly provide dependencies • Greater control over dependency

    • Can’t annotate dependency • Dependency inversion
  56. Providers @Module(injects = Greeter.class)
 public final class MyModule {
 @Provides

    Text provideText() {
 return new Text("Hello, World!");
 }
 
 @Provides Greeter provideGreeter(Text text) {
 return new Greeter(text);
 }
 }
  57. Providers @Module(injects = Greeter.class)
 public final class MyModule {
 @Provides

    Text provideText() {
 return new Text("Hello, World!");
 }
 
 @Provides Greeter provideGreeter(Text text) {
 return new Greeter(text);
 }
 }
  58. Providers @Module(injects = Greeter.class)
 public final class MyModule {
 @Provides

    Text provideText() {
 return new Text("Hello, World!");
 }
 
 @Provides Greeter provideGreeter(Text text) {
 return new Greeter(text);
 }
 }
  59. Providers @Module(injects = Greeter.class)
 public final class MyModule {
 @Provides

    Text provideText() {
 return new Text("Hello, World!");
 }
 
 @Provides Greeter provideGreeter(Text text) {
 return new Greeter(text);
 }
 }
  60. Providing Interfaces

  61. Providing Interfaces public interface Reader {
 char read();
 }
 public

    class KeyboardReader implements Reader {
 @Override
 public char read() { /* implementation */ }
 }
 @Module(injects = Reader.class)
 public final class MyModule {
 @Provides Reader provideReader() {
 return new KeyboardReader();
 }
 }
  62. Providing Interfaces public interface Reader {
 char read();
 }
 public

    class KeyboardReader implements Reader {
 @Override
 public char read() { /* implementation */ }
 }
 @Module(injects = Reader.class)
 public final class MyModule {
 @Provides Reader provideReader() {
 return new KeyboardReader();
 }
 }
  63. Providing Interfaces public interface Reader {
 char read();
 }
 public

    class KeyboardReader implements Reader {
 @Override
 public char read() { /* implementation */ }
 }
 @Module(injects = Reader.class)
 public final class MyModule {
 @Provides Reader provideReader() {
 return new KeyboardReader();
 }
 }
  64. Providing Interfaces public interface Reader {
 char read();
 }
 public

    class KeyboardReader implements Reader {
 @Override
 public char read() { /* implementation */ }
 }
 @Module(injects = Reader.class)
 public final class MyModule {
 @Provides Reader provideReader() {
 return new KeyboardReader();
 }
 }
  65. Providing Interfaces public interface Reader {
 char read();
 }
 public

    class KeyboardReader implements Reader {
 @Override
 public char read() { /* implementation */ }
 }
 @Module(injects = Reader.class)
 public final class MyModule {
 @Provides Reader provideReader() {
 return new KeyboardReader();
 }
 }
  66. Single Module

  67. Multiple Modules

  68. Multiple Modules • Compile time @Module(includes = HttpModule.class)
 public final

    class RestModule { } • Runtime ObjectGraph.create(new RestModule(), new HttpModule()); Warning!
  69. None
  70. Unused! Incomplete!

  71. // HttpModule.java @Module(library = true)
 public final class HttpModule {


    @Provides OkHttp provideOkHttp() {
 return new OkHttp();
 }
 }
 // RestModule.java @Module(complete = false)
 public final class RestModule {
 @Provides Retrofit provideRetrofit(OkHttp okHttp) {
 return new Retrofit(okHttp);
 }
 }
  72. // HttpModule.java @Module(library = true)
 public final class HttpModule {


    @Provides OkHttp provideOkHttp() {
 return new OkHttp();
 }
 }
 // RestModule.java @Module(complete = false)
 public final class RestModule {
 @Provides Retrofit provideRetrofit(OkHttp okHttp) {
 return new Retrofit(okHttp);
 }
 }
  73. // HttpModule.java @Module(library = true)
 public final class HttpModule {


    @Provides OkHttp provideOkHttp() {
 return new OkHttp();
 }
 }
 // RestModule.java @Module(complete = false)
 public final class RestModule {
 @Provides Retrofit provideRetrofit(OkHttp okHttp) {
 return new Retrofit(okHttp);
 }
 }
  74. // HttpModule.java @Module(library = true)
 public final class HttpModule {


    @Provides OkHttp provideOkHttp() {
 return new OkHttp();
 }
 }
 // RestModule.java @Module(complete = false)
 public final class RestModule {
 @Provides Retrofit provideRetrofit(OkHttp okHttp) {
 return new Retrofit(okHttp);
 }
 }
  75. // HttpModule.java @Module(library = true)
 public final class HttpModule {


    @Provides OkHttp provideOkHttp() {
 return new OkHttp();
 }
 }
 // RestModule.java @Module(complete = false)
 public final class RestModule {
 @Provides Retrofit provideRetrofit(OkHttp okHttp) {
 return new Retrofit(okHttp);
 }
 }
  76. // HttpModule.java @Module(library = true)
 public final class HttpModule {


    @Provides OkHttp provideOkHttp() {
 return new OkHttp();
 }
 }
 // RestModule.java @Module(complete = false)
 public final class RestModule {
 @Provides Retrofit provideRetrofit(OkHttp okHttp) {
 return new Retrofit(okHttp);
 }
 }
  77. // HttpModule.java @Module(library = true)
 public final class HttpModule {


    @Provides OkHttp provideOkHttp() {
 return new OkHttp();
 }
 }
 // RestModule.java @Module(complete = false)
 public final class RestModule {
 @Provides Retrofit provideRetrofit(OkHttp okHttp) {
 return new Retrofit(okHttp);
 }
 }
  78. Other Dagger Features • Singletons @Provides @Singleton Greeter provideGreeter() •

    Lazy injecting @Inject Lazy<Text> text; • Qualifiers @Provides @Named("Greeter1") Greeter provideGreeter()
  79. None
  80. Dagger + Gradle dependencies {
 provided 'com.squareup.dagger:dagger-compiler:1.2.2'
 compile 'com.squareup.dagger:dagger:1.2.2'
 }

  81. Dagger + Gradle apply plugin: 'com.neenbedankt.android-apt'
 
 dependencies {
 apt

    'com.squareup.dagger:dagger-compiler:1.2.2'
 compile 'com.squareup.dagger:dagger:1.2.2'
 } https://bitbucket.org/hvisser/android-apt
  82. Dagger + ProGuard • Google ProGuard config • Use Dagger

    2
  83. Injecting Android • Activity, Service, Fragment, etc… • Problem: We

    don’t control construction! • Solution: ObjectGraph.inject()
  84. Injecting Activities public class MyActivity extends Activity {
 
 @Inject

    Dependency dependency;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 ObjectGraph objectGraph = ((MyApplication) getApplication()).getObjectGraph();
 objectGraph.inject(this);
 
 setContentView(R.layout.main);
 }
 }
  85. Injecting Activities public class MyActivity extends Activity {
 
 @Inject

    Dependency dependency;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 ObjectGraph objectGraph = ((MyApplication) getApplication()).getObjectGraph();
 objectGraph.inject(this);
 
 setContentView(R.layout.main);
 }
 }
  86. Injecting Activities public class MyActivity extends Activity {
 
 @Inject

    Dependency dependency;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 ObjectGraph objectGraph = ((MyApplication) getApplication()).getObjectGraph();
 objectGraph.inject(this);
 
 setContentView(R.layout.main);
 }
 }
  87. u2020 • Unicode character for “dagger”: † • Injector pattern

    • https://github.com/JakeWharton/u2020
  88. Thanks! @danlew42 danlew.net