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

Building Testable Mobile Applications with Depe...

Sponsored · SiteGround - Reliable hosting with speed, security, and support you can count on. →

Building Testable Mobile Applications with Dependency Injection

Avatar for Jacob Tabak

Jacob Tabak

May 20, 2015

More Decks by Jacob Tabak

Other Decks in Programming

Transcript

  1. Dependency Injection is a pattern that separates the creation of

    dependencies from the objects that need them.
  2. public class MyCity implements City {
 private final WaterTower waterTower;


    private final CoalPowerPlant coalPowerPlant;
 
 public MyCity() {
 this.waterTower = new WaterTower();
 this.coalPowerPlant = new CoalPowerPlant();
 }
 
 @Override public Water water() {
 return waterTower.water();
 }
 @Override public Electricity electricity() {
 return coalPowerPlant.electricity();
 }
 @Override public Education education() { return null; }
 }
  3. public class MyCity implements City {
 private final WaterTower waterTower;


    private final CoalPowerPlant coalPowerPlant;
 private final ElementarySchool elementarySchool;
 
 public MyCity() {
 this.waterTower = new WaterTower();
 this.coalPowerPlant = new CoalPowerPlant();
 this.elementarySchool = new ElementarySchool();
 }
 
 @Override public Water water() { return waterTower.water(); }
 @Override public Electricity electricity() {
 return coalPowerPlant.electricity();
 }
 @Override public Education education() { return elementarySchool.education(); }
 }
  4. public class MyCity implements City {
 private final WaterSource waterSource;


    private final PowerPlant powerPlant;
 private final SchoolSystem schoolSystem;
 
 public MyCity() {
 this.waterSource = new WaterTower();
 this.powerPlant = new SolarPowerPlant();
 this.schoolSystem = new ElementarySchool();
 }
 
 @Override public Water water() {
 return waterSource.water();
 }
 @Override public Electricity electricity() {
 return powerPlant.electricity();
 }
 @Override public Education education() {
 return schoolSystem.education();
 }
 }
  5. Dependency Injection is a pattern that separates the creation of

    dependencies from the objects that need them.
  6. public class GenericCity implements City {
 private final WaterSource waterSource;


    private final PowerPlant powerPlant;
 private final SchoolSystem schoolSystem;
 
 public GenericCity(WaterSource waterSource, PowerPlant powerPlant, SchoolSystem schoolSystem) {
 this.waterSource = waterSource;
 this.powerPlant = powerPlant;
 this.schoolSystem = schoolSystem;
 }
 
 @Override public Water water() {
 return waterSource.water();
 }
 
 @Override public Electricity electricity() {
 return powerPlant.electricity();
 }
 
 @Override public Education education() {
 return schoolSystem.education();
 }
 } This isn’t new!
  7. Teacher firstGradeTeacher = new Teacher("John Smith");
 Teacher secondGradeTeacher = new

    Teacher("Jane Doe");
 Headmaster headmaster = new Headmaster("Dorothy Sanders");
 Faculty faculty = new Faculty(headmaster, Arrays.asList(firstGradeTeacher, secondGradeTeacher));
 Subject scienceSubject = new Subject("Science");
 Subject mathSubject = new Subject("Math");
 Subject englishSubject = new Subject("English");
 Curriculum curriculum = new Curriculum( Arrays.asList(scienceSubject, mathSubject, englishSubject));
 ElementarySchool elementarySchool = new ElementarySchool(faculty, curriculum);
  8. <?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
 <bean id="city"

    class="GenericCity">
 <constructor-arg ref="electricity"/>
 <constructor-arg ref="water"/>
 <constructor-arg ref="education"/>
 </bean>
 <bean id="electricity" class="CoalPowerPlant"/>
 <bean id="water" class="WaterTower"/>
 <bean id="education" class="ElementarySchool">
 <constructor-arg ref="faculty"/>
 <constructor-arg ref="curriculum"/>
 </bean>
 <bean id="faculty" class="ElementarySchoolFaculty"/>
 <bean id="curriculum" class="ElementarySchoolCurriculum"/>
 </beans>
  9. Pros • Initialization Ordering • Only define dependencies once Cons

    • Verbose XML • No compile-time validation • Poor IDE integration • No traceability • Reflection-based API Spring Framework
  10. public class GenericCity implements City {
 private final WaterSource waterSource;


    private final PowerPlant powerPlant;
 private final SchoolSystem schoolSystem;
 
 @Inject public GenericCity(
 WaterSource waterSource,
 PowerPlant powerPlant,
 SchoolSystem schoolSystem
 ) {
 this.waterSource = waterSource;
 this.powerPlant = powerPlant;
 this.schoolSystem = schoolSystem;
 }
 
 @Override public Water water() {
 return waterSource.water();
 }
 
 @Override public Electricity electricity() {
 return powerPlant.electricity();
 }
 
 @Override public Education education() {
 return schoolSystem.education();
 }
 }
  11. public class SmallCityModule extends AbstractModule {
 @Provides WaterSource provideWaterSource() {

    return new WaterTower(); }
 @Provides PowerPlant providePowerPlant() { return new CoalPowerPlant(); }
 @Provides SchoolSystem provideSchoolSystem() { return new ElementarySchool(); }
 } public class LargeCityModule extends AbstractModule {
 @Provides WaterSource provideWaterSource() { return new PumpingStation(); }
 @Provides PowerPlant providePowerPlant() { return new NuclearPowerPlant(); }
 @Provides SchoolSystem provideSchoolSystem() { return new PublicSchoolSystem(); }
 }
  12. –Friendly Guice Runtime Error No implementation for com.example.PowerPlant was bound

    while locating com.example.PowerPlant for parameter 2 at com.example.GenericCity.<init>(GenericCity.java.9) while locating com.example.GenericCity
  13. Pros • Don’t need to duplicate logic between XML and

    Java • In-line, annotation-based config • Automatically inject concrete types with no additional configuration Cons • No compile-time validation • Poor traceability • Slow reflection-based API Google Guice
  14. @Module(
 includes = { /* other modules */ },
 addsTo

    = { /* other module */ },
 complete = true,
 library = false,
 injects = { MainActivity.class }
 )
 public class LargeCityModule extends AbstractModule {
 @Provides WaterSource provideWaterSource() { return new PumpingStation(); }
 @Provides PowerPlant providePowerPlant() { return new NuclearPowerPlant(); }
 @Provides SchoolSystem provideSchoolSystem() { return new PublicSchoolSystem(); }
 }
  15. Pros • Partial compile-time graph validation • Improved traceability •

    Objects instantiated without reflection • Flexible model (overrides) Cons • Graph composition happens at runtime • Generated code can be hard to read • Does not work with Proguard Dagger 1
  16. @Component(modules = LargeCityModule.class)
 public interface LargeCityComponent {
 // Optional -

    Access dependencies directly
 WaterSource waterSource();
 PowerPlant powerPlant();
 SchoolSystem schoolSystem();
 
 // Contract for field injection
 void inject(MainActivity activity);
 } public class MainActivity extends Activity {
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 LargeCityComponent component = DaggerLargeCityComponent.create();
 component.inject(this);
 }
 }
  17. @Module(
 complete = false,
 library = true
 )
 public class

    ApiModule {
 @Provides @Singleton Endpoint provideEndpoint() {
 return Endpoints.newFixedEndpoint("https://api.github.com");
 }
 }
 
 @Module(
 complete = false,
 library = true,
 overrides = true
 )
 public class DebugApiModule {
 @Provides @Singleton Endpoint provideEndpoint(StringPreference apiEndpointPref) {
 return Endpoints.newFixedEndpoint(apiEndpointPref.get());
 }
 }
  18. Dagger 1 or Dagger 2? Dagger 1 More flexibility Simpler

    build integration Dagger 2 Better performance Compile-time graph validation Proguard-friendly
  19. public class City {
 private final WaterSource waterSource;
 private final

    PowerPlant powerPlant;
 @Inject public City(WaterSource waterSource, PowerPlant powerPlant) {
 this.waterSource = waterSource;
 this.powerPlant = powerPlant;
 } @Override public Water water() { return waterSource.water(); }
 @Override public Electricity electricity() { return powerPlant.electricity(); }
 @Override public Education education() { return schoolSystem.education(); }
 } Constructor Injection (for most classes)
  20. public class MainActivity extends Activity {
 @Inject OkHttpClient okHttpClient;
 


    @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 Injector.obtain(getApplicationContext()).inject(this);
 }
 } Field Injection (for activities/fragments)
  21. public class MyCustomView extends FrameLayout {
 private final Picasso picasso;


    
 public MyCustomView(Context context) {
 super(context);
 picasso = Injector.obtain(context).getPicasso();
 }
 } Method Injection
  22. @Provides @Singleton Picasso providePicasso(Application application) {
 return Picasso.with(application);
 }
 


    @Provides DateTime provideCurrentTime() {
 return new DateTime();
 } Dependency Scope
  23. @Module
 class UserModule {
 @Provides @Named("username") String providerUsername(SharedPreferences preferences) {


    return preferences.getString("username", null);
 }
 
 @Provides @Named("password") String providePassword(SharedPreferences preferences) {
 return preferences.getString("password", null);
 }
 } class LoginActivity extends Activity {
 @Inject @Named("username") String username;
 @Inject @Named("password") String password;
 } Qualifiers
  24. public class ContentStatTests {
 @Mock SmsReader smsReader;
 @Mock MediaStoreReader mediaStoreReader;


    ContentStatClient statClient;
 
 @Before
 public void initialize() {
 MockitoAnnotations.initMocks(this);
 statClient = new ContentStatClient(mediaStoreReader, smsReader);
 }
 
 @Test
 public void testContentCount() {
 when(smsReader.getSmsCount()).thenReturn(1);
 when(mediaStoreReader.getPhotoCount()).thenReturn(3);
 when(mediaStoreReader.getVideoCount()).thenReturn(5);
 assertThat(statClient.getCount(), equalTo(9));
 }
 }
  25. public class ClockActivityTest {
 @Inject Clock clock;
 
 @Before
 public

    void setUp() {
 MyApplication app = (MyApplication) getApplication();
 TestComponent component = DaggerTestComponent.create();
 app.setComponent(component);
 component.inject(this);
 }
 
 @Test
 public void testClockAtNoon() {
 when(clock.now()).thenReturn(new LocalTime().withHourOfDay(12).withMinuteOfHour(0));
 getActivity();
 onView(withId(R.id.date_view)).check(matches(withText("12:00")));
 }
 }
 
 @Module
 class MockClockModule {
 @Provides Clock provideClock() {
 return mock(Clock.class);
 }
 }
 
 @Component(modules = MockClockModule.class)
 interface TestClockComponent extends ClockComponent {
 void inject(ClockActivityTest testClass);
 }
  26. public class TraditionalActivity extends Activity { @Override protected void onCreate(Bundle

    state) {
 super.onCreate(state);
 setContentView(R.layout.activity_main);
 } } Traditional Activity Layout public class U2020Activity extends Activity { @Inject AppContainer appContainer;
 @Override protected void onCreate(Bundle state) {
 super.onCreate(state); Injector.obtain(getApplication()).inject(this); ViewGroup container = appContainer.bind(this); getLayoutInflater().inflate( R.layout.activity_main, container);
 } } u2020 Activity Layout