Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
@NYTDevs | developers.nytimes.com
Mike Nakhimovich @FriendlyMikhail
Android Framework Team
Swordfighting with Dagger
Depen...
What is Dagger?
Alternative way to instantiate and manage your
objects
● Guice - Google (Dagger v.0)
● Dagger 1 - Square
●...
Why Do We Need It?
Good Code = Testable Code
Why Do We Need It?
More Tests = Less Anxiety
Why Do We Need It?
Proper Code Organization is
a requirement for testing
Untestable Code (Me in the Beginning)
public class MyClass {
private Model model;
public MyClass() {this.model = new Model...
Internet Told Me to Externalize My Dependencies
public class MyClass {
...
public MyClass(Model model) {this.model = model...
Where Does Model Come From?
Dependency Injection
to the rescue!
Dagger Helps You Externalize Object Creation
@Provides
Model provideModel(){
return new Model();
}
Provide from Modules
@Module
public class AppModule{
}
A module is a part of your application that provides some
functiona...
Provide from Modules
@Module
public class AppModule{
@Provides
Model provideModel(){
return new Model();
}
...
A module is...
Components are Composed of Modules
@Singleton
@Component(modules = {MyModule.class})
public interface AppComponent {
void ...
Next, Create a Component Instance
component = DaggerAppComponent.builder()
.myModule(new MyModule(this))
.build();
Register with Component
protected void onCreate(Bundle savedInstanceState) {
getApplicationComponent().inject(this);
Injection Fun
Now you can inject dependencies as fields or
constructor arguments
@Inject
Model model;
@Inject
public Prese...
Dagger @ NY Times
Now for the
fun stuff!
50% Recipes 50% Ramblings
Dagger @ NY Times
● Module/Component Architecture
○ Working with libraries
○ Build Types & Flavors
● Scopes
○ Application
...
Code Organization
How Dagger manages 6 build variants & 6+ libraries
GoogleDebug
AmazonDebug
GoogleBetaAmazonBeta
GoogleRe...
Application Scoped Modules
● App Module
● Build Type Module
● Library Module
● Flavor Module
App Module Singletons
○ Parser (configured GSON)
App Module Singletons
○ Parser (configured GSON)
○ IO Managers
App Module Singletons
○ Parser (configured GSON)
○ IO Managers
○ Configs (Analytics,AB, E-Commerce)
Example Library Module: E-Commerce
@Module
public class ECommModule {
@Provides
@Singleton
public ECommBuilder provideECom...
E-Comm using App Module’s Dep
@Module
public class ECommModule {
@Provides
@Singleton
public ECommBuilder provideECommBuil...
Amazon & Google Flavors
● Amazon Variants needs Amazon E-Commerce
● Google Variants needs to contain Google E-Commerce
How...
E-Comm Qualified Provider
@Module public class ECommModule {
@Provides @Singleton
public ECommBuilder provideECommBuilder(...
E-Comm Qualified Provider
@Module public class ECommModule {
@Provides @Singleton
public ECommBuilder provideECommBuilder(...
Flavor Module: src/google & src/Amazon
@Module public class FlavorModule {
}
Flavor Module Provides Non-Qualified E-Comm
@Module public class FlavorModule {
@Singleton
@Provides
ECommManager provideE...
Type Module
Brings build specific dependencies/providers in Type Module
Type Module
Brings build specific dependencies/providers in Type Module
○ Logging
■ Most logging for Beta Build
■ No-Op Re...
Type Module
Brings build specific dependencies/providers in Type Module
○ Logging
■ Most logging for Beta Build
■ No-Op Re...
Type Module
● Brings build specific dependencies/providers in Type Module
○ Logging
■ Most logging for Beta Build
■ No-Op ...
Component Composition
How we combine our modules
Start with Base Component
● Base Component lives in src/main
● Contains inject(T t) for classes & Services that register
w...
Src/Google & Src/Amazon Contain a FlavorComponent
● Create FlavorComponent that inherits from BaseComponent
● Register inj...
App Component debug, beta, release
One for src/debug src/beta src/release
public interface ApplicationComponent {
}
App Component
Inherits from Flavor Component
public interface ApplicationComponent extends FlavorComponent {
}
App Component
● Adds @Component @Singleton annotations
@Singleton @Component
public interface ApplicationComponent extends...
App Component
● Adds modules
@Singleton @Component(modules =
{ApplicationModule.class, FlavorModule.class, TypeModule.clas...
Anything registering with App Component
gains access to all providers for the Flavor/Type
Usage of Generated App
Component
App Component Factory
public class ComponentFactory {
public AppComponent buildComponent(Application context) {
return com...
Component Instance
● NYT Application retains component
private void buildComponentAndInject() {
appComponent = componentFa...
Introducing Activity Scope
Activity Component
● Inherits all “provides” from App Component
● Allows you to add “Activity Singletons”
○ 1 Per Activity...
ActivityComponent
@Subcomponent(modules = {ActivityModule.class, BundleModule.class})
@ScopeActivity
public interface Acti...
ActivityComponentFactory
public final class ActivityComponentFactory {
public static ActivityComponent create(Activity act...
Activity Component Injection
public void onCreate(@Nullable Bundle savedInstanceState) {
activityComponent = ActivityCompo...
Activity Component Modules
Activity Module
● Publish Subjects (mini bus)
● Reactive Text Resizer
● Snack Bar Util
Font Resizing
@Provides @ScopeActivity @FontBus
PublishSubject<Integer> provideFontChangeBus() {
return PublishSubject.cre...
Usage of Font Resizer
@Inject
FontResizer fontResizer;
private void registerForFontResizing(View itemView) {
fontResizer.r...
Usage of Font Resize “Bus”
@Inject
public SectionPresenter(@FontBus PublishSubject<Integer> fontBus) {
fontBus.subscribe(f...
SnackBarUtil
@ScopeActivity
public class SnackbarUtil {
@Inject Activity activity;
public Snackbar makeSnackbar(String txt...
Bundle Module, A Love Story
Bundle Management
Passing intent arguments to fragments/views is painful
● Need to save state
● Complexity with nested fra...
Create Bundle Service
public class BundleService {
private final Bundle data;
public BundleService(Bundle savedState, Bund...
Instantiate Bundle Service in Activity
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
bundleServ...
Bind Bundle Service to Bundle Module
@Provides
@ScopeActivity
public BundleService provideBundleService(Activity context)
...
Provide Individualized Intent Values
@Provides
@ScopeActivity
@AssetId
public Long provideArticleId(BundleService bundleSe...
Inject Intent Values Directly into Views & Presenters
@Inject
public CommentPresenter(@AssetId String assetId){
//fetch co...
Old Way
Normally we would have to pass assetId from:
ArticleActivity to
ArticleFragment to
CommentFragment to
CommentView ...
Testing
Simple Testing
JUNIT Mockito, AssertJ
@Mock
AppPreferences prefs;
@Before public void setUp() {
inboxPref = new InboxPrefe...
Testing with Dagger
Dagger BaseTestCase
public abstract class BaseTestCase extends TestCase {
protected TestComponent getTestComponent() {
fin...
TestComponent
@Singleton
@Component(modules = {TestModule.class,ApplicationModule.class,
FlavorModule.class, TypeModule.cl...
Dagger Test with Mocks
public class WebViewUtilTest extends BaseTestCase {
@Inject NetworkStatus networkStatus;
@Inject We...
Dagger Test with Mocks
public class WebViewUtilTest extends BaseTestCase {
@Inject NetworkStatus networkStatus;
@Inject We...
Dagger Test with Mocks Gotchas
● Must have provides method
● Must be in module you are explicitly passing into Dagger
Functional/Espresso Testing
NYTFunctionalTestApp
● Creates Component with overridden providers
NYTFunctionalTestApp
● Creates Component with overridden providers
● Mostly no-op since this is global
■ Analytics
■ AB Ma...
NYTFunctionalTestApp
● Creates Component with overridden providers
● Mostly no-op since this is global
■ Analytics
■ AB Ma...
NYTFunctionalTestApp
● Creates Component with overridden providers
● Mostly no-op since this is global
■ Analytics
■ AB Ma...
NYTFunctionalTestApp
public class NYTFunctionalTestsApp extends NYTApplication {
ComponentFactory componentFactory(Applica...
NYTFunctionalTestRunner
public class NYTFunctionalTestsRunner extends AndroidJUnitRunner {
@Override
public Application ne...
Sample Espresso Test
@RunWith(AndroidJUnit4.class)
public class MainScreenTests {
@Test
public void openMenuAndCheckItems(...
@NYTDevs | developers.nytimes.com
Questions?
@NYTDevs | developers.nytimes.com
Thank You!
(We’re hiring)
@FriendlyMikhail
Upcoming SlideShare
Loading in …5
×

Advanced Dagger talk from 360andev

2,506 views

Published on

Slides from 360Andev Dagger talk

Published in: Software

Advanced Dagger talk from 360andev

  1. 1. @NYTDevs | developers.nytimes.com Mike Nakhimovich @FriendlyMikhail Android Framework Team Swordfighting with Dagger Dependecy Injection Made Less Simple
  2. 2. What is Dagger? Alternative way to instantiate and manage your objects ● Guice - Google (Dagger v.0) ● Dagger 1 - Square ● Dagger 2 - Back to Google :-)
  3. 3. Why Do We Need It? Good Code = Testable Code
  4. 4. Why Do We Need It? More Tests = Less Anxiety
  5. 5. Why Do We Need It? Proper Code Organization is a requirement for testing
  6. 6. Untestable Code (Me in the Beginning) public class MyClass { private Model model; public MyClass() {this.model = new Model();} public String getName() {return model.getName();} } How can we test if model.getName() was called?
  7. 7. Internet Told Me to Externalize My Dependencies public class MyClass { ... public MyClass(Model model) {this.model = model;} public String getName() {return model.getName();} }... public void testGetData(){ Model model = mock(Model.class); when(model.getName()).thenReturn("Mike"); MyClass myClass = new MyClass(model).getName(); verify(myClass.getName()).isEq("Mike"); }
  8. 8. Where Does Model Come From? Dependency Injection to the rescue!
  9. 9. Dagger Helps You Externalize Object Creation @Provides Model provideModel(){ return new Model(); }
  10. 10. Provide from Modules @Module public class AppModule{ } A module is a part of your application that provides some functionality.
  11. 11. Provide from Modules @Module public class AppModule{ @Provides Model provideModel(){ return new Model(); } ... A module is a part of your application that provides some functionality.
  12. 12. Components are Composed of Modules @Singleton @Component(modules = {MyModule.class}) public interface AppComponent { void inject(MyActivity activity); } A Component is the manager of all your module providers
  13. 13. Next, Create a Component Instance component = DaggerAppComponent.builder() .myModule(new MyModule(this)) .build();
  14. 14. Register with Component protected void onCreate(Bundle savedInstanceState) { getApplicationComponent().inject(this);
  15. 15. Injection Fun Now you can inject dependencies as fields or constructor arguments @Inject Model model; @Inject public Presenter(Model model)
  16. 16. Dagger @ NY Times Now for the fun stuff! 50% Recipes 50% Ramblings
  17. 17. Dagger @ NY Times ● Module/Component Architecture ○ Working with libraries ○ Build Types & Flavors ● Scopes ○ Application ○ Activity (Now with Singletons!) ● Testing ○ Espresso ○ Unit Testing
  18. 18. Code Organization How Dagger manages 6 build variants & 6+ libraries GoogleDebug AmazonDebug GoogleBetaAmazonBeta GoogleRelease AmazonRelease
  19. 19. Application Scoped Modules ● App Module ● Build Type Module ● Library Module ● Flavor Module
  20. 20. App Module Singletons ○ Parser (configured GSON)
  21. 21. App Module Singletons ○ Parser (configured GSON) ○ IO Managers
  22. 22. App Module Singletons ○ Parser (configured GSON) ○ IO Managers ○ Configs (Analytics,AB, E-Commerce)
  23. 23. Example Library Module: E-Commerce @Module public class ECommModule { @Provides @Singleton public ECommBuilder provideECommBuilder( )
  24. 24. E-Comm using App Module’s Dep @Module public class ECommModule { @Provides @Singleton public ECommBuilder provideECommBuilder(ECommConfig config){ return new ECommManagerBuilder().setConfig(config); }
  25. 25. Amazon & Google Flavors ● Amazon Variants needs Amazon E-Commerce ● Google Variants needs to contain Google E-Commerce How can Dagger help?
  26. 26. E-Comm Qualified Provider @Module public class ECommModule { @Provides @Singleton public ECommBuilder provideECommBuilder(ECommConfig config){ return new ECommManagerBuilder().setConfig(config); } @Provides @Singleton @Google public ECommManager providesGoogleEComm (ECommBuilder builder, GooglePayments googlePayments)
  27. 27. E-Comm Qualified Provider @Module public class ECommModule { @Provides @Singleton public ECommBuilder provideECommBuilder(ECommConfig config){ return new ECommManagerBuilder().setConfig(config); } ... @Provides @Singleton @Amazon public ECommManager providesAmazonEComm (ECommBuilder builder, AmazonPayments amazonPayments)
  28. 28. Flavor Module: src/google & src/Amazon @Module public class FlavorModule { }
  29. 29. Flavor Module Provides Non-Qualified E-Comm @Module public class FlavorModule { @Singleton @Provides ECommManager provideECommManager(@Google ECommManager ecomm) } Note: Proguard strips out the other impl from Jar :-)
  30. 30. Type Module Brings build specific dependencies/providers in Type Module
  31. 31. Type Module Brings build specific dependencies/providers in Type Module ○ Logging ■ Most logging for Beta Build ■ No-Op Release
  32. 32. Type Module Brings build specific dependencies/providers in Type Module ○ Logging ■ Most logging for Beta Build ■ No-Op Release ○ Payments ■ No-Op for debug
  33. 33. Type Module ● Brings build specific dependencies/providers in Type Module ○ Logging ■ Most logging for Beta Build ■ No-Op Release ○ Payments ■ No-Op for debug ○ Device ID ■ Static for Debug
  34. 34. Component Composition How we combine our modules
  35. 35. Start with Base Component ● Base Component lives in src/main ● Contains inject(T t) for classes & Services that register with Dagger (non flavor/build specific) interface BaseComponent { void inject(NYTApplication target); }
  36. 36. Src/Google & Src/Amazon Contain a FlavorComponent ● Create FlavorComponent that inherits from BaseComponent ● Register inject for flavor specific classes ● Anything not in src/flavor that needs component registers here ie: ○ Messaging Service ○ Payment Activity public interface FlavorComponent extends BaseComponent { void inject(ADMessaging target); }
  37. 37. App Component debug, beta, release One for src/debug src/beta src/release public interface ApplicationComponent { }
  38. 38. App Component Inherits from Flavor Component public interface ApplicationComponent extends FlavorComponent { }
  39. 39. App Component ● Adds @Component @Singleton annotations @Singleton @Component public interface ApplicationComponent extends FlavorComponent { }
  40. 40. App Component ● Adds modules @Singleton @Component(modules = {ApplicationModule.class, FlavorModule.class, TypeModule.class, AnalyticsModule.class, ECommModule.class, PushClientModule.class }) public interface ApplicationComponent extends FlavorComponent { }
  41. 41. Anything registering with App Component gains access to all providers for the Flavor/Type
  42. 42. Usage of Generated App Component
  43. 43. App Component Factory public class ComponentFactory { public AppComponent buildComponent(Application context) { return componentBuilder(context).build(); } // We override it for functional tests. DaggerApplicationComponent.Builder componentBuilder(Application context) { return DaggerApplicationComponent.builder() .applicationModule(new ApplicationModule(context)} }
  44. 44. Component Instance ● NYT Application retains component private void buildComponentAndInject() { appComponent = componentFactory().buildComponent(this); appComponent.inject(this); } public ComponentFactory componentFactory() { return new ComponentFactory(); }
  45. 45. Introducing Activity Scope
  46. 46. Activity Component ● Inherits all “provides” from App Component ● Allows you to add “Activity Singletons” ○ 1 Per Activity ○ Many views/fragments within activity can inject same instance
  47. 47. ActivityComponent @Subcomponent(modules = {ActivityModule.class, BundleModule.class}) @ScopeActivity public interface ActivityComponent { void inject(ArticleView view); } Add to AppComponent: Activitycomponent plusActivityComponent(ActivityModule activityModule);
  48. 48. ActivityComponentFactory public final class ActivityComponentFactory { public static ActivityComponent create(Activity activity) { return ((NYTApp)activity.getApplicationContext).getComponent() .plusActivityComponent(new ActivityModule(activity)); } }
  49. 49. Activity Component Injection public void onCreate(@Nullable Bundle savedInstanceState) { activityComponent = ActivityComponentFactory.create(this); activityComponent.inject(this);
  50. 50. Activity Component Modules
  51. 51. Activity Module ● Publish Subjects (mini bus) ● Reactive Text Resizer ● Snack Bar Util
  52. 52. Font Resizing @Provides @ScopeActivity @FontBus PublishSubject<Integer> provideFontChangeBus() { return PublishSubject.create(); } @Provides @ScopeActivity FontResizer provideFontResize( @FontBus PublishSubject<Integer> fontBus) { return new FontResizer(fontBus); }
  53. 53. Usage of Font Resizer @Inject FontResizer fontResizer; private void registerForFontResizing(View itemView) { fontResizer.registerResize(itemView); }
  54. 54. Usage of Font Resize “Bus” @Inject public SectionPresenter(@FontBus PublishSubject<Integer> fontBus) { fontBus.subscribe(fontSize -> handleFontHasChanged()); } Dagger helps us inject only what we need
  55. 55. SnackBarUtil @ScopeActivity public class SnackbarUtil { @Inject Activity activity; public Snackbar makeSnackbar(String txt, int duration) { return Snackbar.make(...);} } …. In some presenter class: public void onError(Throwable error) { snackbarUtil.makeSnackbar(SaveHandler.SAVE_ERROR, SHORT).show(); }
  56. 56. Bundle Module, A Love Story
  57. 57. Bundle Management Passing intent arguments to fragments/views is painful ● Need to save state ● Complexity with nested fragments ● Why we not inject intent arguments instead?
  58. 58. Create Bundle Service public class BundleService { private final Bundle data; public BundleService(Bundle savedState, Bundle intentExtras) { data = new Bundle(); if (savedState != null) { data.putAll(savedState); } if (intentExtras != null) { data.putAll(intentExtras); } }
  59. 59. Instantiate Bundle Service in Activity @Override protected void onCreate(@Nullable Bundle savedInstanceState) { bundleService = new BundleService(savedInstanceState, getIntent().getExtras ()); //Never have to remember to save instance state again! protected void onSaveInstanceState(Bundle outState) { outState.putAll(bundleService.getAll());
  60. 60. Bind Bundle Service to Bundle Module @Provides @ScopeActivity public BundleService provideBundleService(Activity context) { return ((Bundler) context).getBundleService(); }
  61. 61. Provide Individualized Intent Values @Provides @ScopeActivity @AssetId public Long provideArticleId(BundleService bundleService) { return bundleService.get(ArticleActivity.ASSET_ID); }
  62. 62. Inject Intent Values Directly into Views & Presenters @Inject public CommentPresenter(@AssetId String assetId){ //fetch comments for current article }
  63. 63. Old Way Normally we would have to pass assetId from: ArticleActivity to ArticleFragment to CommentFragment to CommentView to CommentPresenter :-l
  64. 64. Testing
  65. 65. Simple Testing JUNIT Mockito, AssertJ @Mock AppPreferences prefs; @Before public void setUp() { inboxPref = new InboxPreferences(prefs); } @Test public void testGetUserChannelPreferencesEmpty() { when(prefs.getPreference(IUSER_CHANNELS,emptySet())) .thenReturn(null); assertThat(inboxPref.getUserChannel()).isEmpty(); }
  66. 66. Testing with Dagger
  67. 67. Dagger BaseTestCase public abstract class BaseTestCase extends TestCase { protected TestComponent getTestComponent() { final ApplicationModule applicationModule = getApplicationModule(); return Dagger2Helper.buildComponent( TestComponent.class, applicationModule));}
  68. 68. TestComponent @Singleton @Component(modules = {TestModule.class,ApplicationModule.class, FlavorModule.class, TypeModule.class, AnalyticsModule.class, EcommModule. class, PushModule.class}) public interface TestComponent { void inject(WebViewUtilTest test);
  69. 69. Dagger Test with Mocks public class WebViewUtilTest extends BaseTestCase { @Inject NetworkStatus networkStatus; @Inject WebViewUtil webViewUtil; protected ApplicationModule getApplicationModule() { return new ApplicationModule(application) { protected NetworkStatus provideNetworkStatus() { return mock(NetworkStatus.class); } }; }
  70. 70. Dagger Test with Mocks public class WebViewUtilTest extends BaseTestCase { @Inject NetworkStatus networkStatus; @Inject WebViewUtil webViewUtil; … @Test public void testNoValueOnOffline() throws Exception { when(networkStatus.isInternetConnected()).thenReturn(false); webViewUtil.getIntentLauncher().subscribe(intent -> {fail("intent was launched");});}
  71. 71. Dagger Test with Mocks Gotchas ● Must have provides method ● Must be in module you are explicitly passing into Dagger
  72. 72. Functional/Espresso Testing
  73. 73. NYTFunctionalTestApp ● Creates Component with overridden providers
  74. 74. NYTFunctionalTestApp ● Creates Component with overridden providers ● Mostly no-op since this is global ■ Analytics ■ AB Manager ■ Other Test impls (network, disk)
  75. 75. NYTFunctionalTestApp ● Creates Component with overridden providers ● Mostly no-op since this is global ■ Analytics ■ AB Manager ■ Other Test impls (network, disk) ● Functional test runner uses custom FunctTestApp
  76. 76. NYTFunctionalTestApp ● Creates Component with overridden providers ● Mostly no-op since this is global ■ Analytics ■ AB Manager ■ Other Test impls (network, disk) ● Functional test runner uses custom FunctTestApp ● Test run end to end otherwise
  77. 77. NYTFunctionalTestApp public class NYTFunctionalTestsApp extends NYTApplication { ComponentFactory componentFactory(Application context) { return new ComponentFactory() { protected DaggerApplicationComponent.Builder componentBuilder(Application context) { return super.componentBuilder(context) .applicationModule(new ApplicationModule(NYTFunctionalTestsApp.this) { protected ABManager provideABManager() { return new NoOpABManager(); }
  78. 78. NYTFunctionalTestRunner public class NYTFunctionalTestsRunner extends AndroidJUnitRunner { @Override public Application newApplication(ClassLoader cl,String className, Context context) { return newApplication(NYTFunctionalTestsApp.class, context); } }
  79. 79. Sample Espresso Test @RunWith(AndroidJUnit4.class) public class MainScreenTests { @Test public void openMenuAndCheckItems() { mainScreen .openMenuDialog() .assertMenuDialogContainsItemWithText(R.string.dialog_menu_font_resize) .assertMenuDialogContainsItemWithText(R.string.action_settings); }
  80. 80. @NYTDevs | developers.nytimes.com Questions?
  81. 81. @NYTDevs | developers.nytimes.com Thank You! (We’re hiring) @FriendlyMikhail

×