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
You’ve finished this document.
Download and read it offline.
Upcoming SlideShare
Sword fighting with Dagger GDG-NYC Jan 2016
Next
Upcoming SlideShare
Sword fighting with Dagger GDG-NYC Jan 2016
Next
Download to read offline and view in fullscreen.

Share

Advanced Dagger talk from 360andev

Download to read offline

Slides from 360Andev Dagger talk

Related Books

Free with a 30 day trial from Scribd

See all

Related Audiobooks

Free with a 30 day trial from Scribd

See all

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
  • frizzoso

    Jul. 6, 2018
  • luisfernandezbr

    Apr. 30, 2017
  • TarasBazyshyn

    Nov. 22, 2016
  • suleymanccelik

    Aug. 1, 2016

Slides from 360Andev Dagger talk

Views

Total views

3,189

On Slideshare

0

From embeds

0

Number of embeds

20

Actions

Downloads

45

Shares

0

Comments

0

Likes

4

×