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.

Building maintainable app

1,570 views

Published on

MVP + Dagger2 + tests

Published in: Software

Building maintainable app

  1. 1. Building maintainable app with MVP and Dagger2 KRISTIJAN JURKOVIĆ ANDROID TEAM LEAD @ INFINUM
  2. 2. We're an independent design & development agency.
  3. 3. INFINUM • 90 people in 3 offices • 15 android developers • hundreds of projects
  4. 4. OUR BIGGEST ISSUES?
  5. 5. “Sometimes when you fill a vacuum, it still sucks.” ― Dennis Ritchie
  6. 6. MVP TO THE RESCUE?
  7. 7. PROGRAM TO INTERFACES NOT IMPLEMENTATIONS
  8. 8. ModelPresenterView
  9. 9. LoginActivity LoginPresenterImpl LoginInteractorImpl LoginView LoginPresenter LoginInteractor showLoading() hideLoading() setUsernameError() setPasswordError() showLoading() hideLoading() setUsernameError() setPasswordError() login(username, pass) loginPresenter loginView loginInteractor login(username, pass) login(username, pass, listener) login(username, pass, listener) VIEW PRESENTER MODEL
  10. 10. public interface HomeView { ... } public interface HomePresenter { ... } public interface CurrencyInteractor { ... } VIEW PRESENTER MODEL
  11. 11. public interface HomeView { ... } VIEW
  12. 12. public class HomeActivity extends BaseActivity implements HomeView { // this is an interface HomePresenter presenter; ... }
  13. 13. public interface HomePresenter { ... } PRESENTER
  14. 14. public class HomePresenterImpl implements HomePresenter { // interface private HomeView view; // and another interface private CurrencyInteractor interactor; public HomePresenterImpl( HomeView view, CurrencyInteractor interactor) { this.view = view; this.interactor = interactor; } ... }
  15. 15. public interface CurrencyInteractor { ... } MODEL
  16. 16. public class CurrencyInteractorImpl implements CurrencyInteractor { ... }
  17. 17. HOW SHOULD I GET MY CONTENT?
  18. 18. public interface HomeView { void showCurrencies(List<Currency> currencies); } public interface HomePresenter { void loadCurrencyList(); } public interface CurrencyInteractor { void getCurrencyList(CurrencyListener listener); }
  19. 19. public class HomeActivity extends BaseActivity implements HomeView { private void init() { presenter = new HomePresenterImpl(this, new CurrencyInteractorImpl()); presenter.getCurrencyList(); } @Override public void showCurrencies(List<Currency> currencies) { // display data 
 } }
  20. 20. public class HomePresenterImpl implements HomePresenter { ... @Override public void loadCurrencyList() { interactor.getCurrencyList(...); } }
  21. 21. public class CurrencyInteractorImpl implements CurrencyInteractor { ... @Override public void getCurrencyList( CurrencyListener listener) { // do API/DB call // return result with listener } }
  22. 22. VIEW SHOULDN’T CREATE ITS DEPENDENCIES
  23. 23. DEPENDENCY INJECTION
  24. 24. JSR 330 • 5 annotations - @Named, @Inject, @Qualifier, @Scope, @Singleton • 1 interface - Provider<T>
  25. 25. DAGGER2 TO THE RESCUE
  26. 26. DAGGER 2 • @Module, @Provides, @Component, @Subcomponent, ScopedProvider • Injection into Fields, Constructors, Methods • Each @Inject has to have its @Provides
  27. 27. APP COMPONENT HOST MODULE CONVERTER MODULE CLIENT MODULE LOGGER MODULE API MODULE GSON MODULE
  28. 28. APP COMPONENT HOST MODULE CONVERTER MODULE CLIENT MODULE LOGGER MODULE API MODULE GSON MODULE
  29. 29. @Module public class ApiModule { @Provides @Singleton public ApiService provideApiService( OkHttpClient client, BaseUrl endpoint, Converter.Factory converter) { return RestUtils.createApiService( client, endpoint, converter, ApiService.class); } }
  30. 30. @Module public class ApiModule { @Provides @Singleton public ApiService provideApiService( OkHttpClient client, BaseUrl endpoint, Converter.Factory converter) { return RestUtils.createApiService( client, endpoint, converter, ApiService.class); } }
  31. 31. @Module public class ApiModule { @Provides @Singleton public ApiService provideApiService( OkHttpClient client, BaseUrl endpoint, Converter.Factory converter) { return RestUtils.createApiService( client, endpoint, converter, ApiService.class); } }
  32. 32. @Module public class GsonConverterModule { @Provides @Singleton public Converter.Factory provideConverter(Gson gson) { return GsonConverterFactory.create(gson); } }
  33. 33. APP COMPONENT HOST MODULE CONVERTER MODULE CLIENT MODULE LOGGER MODULE API MODULE GSON MODULE EXECUTORS MODULE
  34. 34. @Component(modules = { HostModule.class, GsonConverterModule.class, ClientModule.class, LoggerModule.class, ExecutorsModule.class, ApiModule.class, GsonModule.class }) @Singleton public interface AppComponent { }
  35. 35. public class MyApplication extends Application { protected AppComponent appComponent; protected void init() { appComponent = DaggerAppComponent.create(); } }
  36. 36. HOW CAN WE REUSE THAT IN OUR ACTIVITIES?
  37. 37. public class HomeActivity extends BaseActivity implements HomeView { private void init() { presenter = new HomePresenterImpl(this, new CurrencyInteractorImpl()); presenter.getCurrencyList(); } @Override public void showCurrencies(List<Currency> currencies) { // display data 
 } }
  38. 38. • Inject presenter into view • Inject view and interactor into presenter
  39. 39. @Module public class HomeModule { private HomeView view; public HomeModule(HomeView view) { this.view = view; } @Provides public HomeView provideView() { return view; } @Provides public HomePresenter providePresenter(HomePresenterImpl presenter) { return presenter; } @Provides public CurrencyInteractor provideInteractor( CurrencyInteractorImpl interactor) { return interactor; } }
  40. 40. @Module public class HomeModule { private HomeView view; public HomeModule(HomeView view) { this.view = view; } @Provides public HomeView provideView() { return view; } @Provides public HomePresenter providePresenter(HomePresenterImpl presenter) { return presenter; } @Provides public CurrencyInteractor provideInteractor( CurrencyInteractorImpl interactor) { return interactor; } }
  41. 41. @Module public class HomeModule { private HomeView view; public HomeModule(HomeView view) { this.view = view; } @Provides public HomeView provideView() { return view; } @Provides public HomePresenter providePresenter(HomePresenterImpl presenter) { return presenter; } @Provides public CurrencyInteractor provideInteractor( CurrencyInteractorImpl interactor) { return interactor; } }
  42. 42. public class CurrencyInteractorImpl implements CurrencyInteractor { @Inject public CurrencyInteractorImpl(ApiService service) { } }
  43. 43. public class CurrencyInteractorImpl implements CurrencyInteractor { @Inject public CurrencyInteractorImpl(ApiService service) { } }
  44. 44. @Module public class HomeModule { private HomeView view; public HomeModule(HomeView view) { this.view = view; } @Provides public HomeView provideView() { return view; } @Provides public HomePresenter providePresenter(HomePresenterImpl presenter) { return presenter; } @Provides public CurrencyInteractor provideInteractor( CurrencyInteractorImpl interactor) { return interactor; } }
  45. 45. public class HomePresenterImpl implements HomePresenter { @Inject public HomePresenterImpl(HomeView view, CurrencyInteractor interactor) { this.view = view; this.interactor = interactor; } }
  46. 46. public class HomeActivity extends BaseActivity implements HomeView { @Inject HomePresenter presenter; }
  47. 47. @Subcomponent(modules = HomeModule.class) public interface HomeComponent { void inject(HomeActivity activity); }
  48. 48. WHAT’S THAT “SUBCOMPONENT” THING YOU MENTIONED?
  49. 49. APP COMPONENT HOST MODULE CONVERTER MODULE CLIENT MODULE LOGGER MODULE API MODULE GSON MODULE HOME MODULE HOMECOMPONENT EXECUTORS MODULE
  50. 50. @Component(modules = { ... }) @Singleton public interface AppComponent { HomeComponent plus(HomeModule module); }
  51. 51. public abstract class BaseActivity extends AppCompatActivity { Override protected void onCreate(Bundle savedInstanceState) { ... injectDependencies(MyApplication.getAppComponent()); } protected abstract void injectDependencies(AppComponent appComponent); }
  52. 52. public class HomeActivity extends BaseActivity implements HomeView { protected void injectDependencies(AppComponent appComponent) { appComponent .plus(new HomeModule(this)) .inject(this); } }
  53. 53. APP COMPONENT HOST MODULE CONVERTER MODULE CLIENT MODULE LOGGER MODULE API MODULE GSON MODULE HOME MODULE HOMECOMPONENT SESSIONCOMPONENT SESSION MODULE EXECUTORS MODULE
  54. 54. SATISFACTION LEVEL 9001
  55. 55. “If you don’t like testing your product, most likely your customers won’t like to test it either.”
  56. 56. APP COMPONENT HOST MODULE CONVERTER MODULE CLIENT MODULE LOGGER MODULE API MODULE GSON MODULE EXECUTORS MODULE
  57. 57. APP COMPONENT HOST MODULE CONVERTER MODULE CLIENT MODULE LOGGER MODULE API MODULE GSON MODULE EXECUTORS MODULE
  58. 58. APPTESTCOMPONENT MOCKHOST MODULE CONVERTER MODULE CLIENT MODULE LOGGER MODULE API MODULE GSON MODULE SYNC EXECUTORS MODULE
  59. 59. @Component(modules = { ... MockHostModule.class, SynchronousExecutorsModule.class, ... }) @Singleton public interface AppTestComponent extends AppComponent { void inject(MyTestApplication app); }
  60. 60. @Component(modules = { ... MockHostModule.class, SynchronousExecutorsModule.class, ... }) @Singleton public interface AppTestComponent extends AppComponent { void inject(MyTestApplication app); }
  61. 61. @Component(modules = { ... MockHostModule.class, SynchronousExecutorsModule.class, ... }) @Singleton public interface AppTestComponent extends AppComponent { void inject(MyTestApplication app); }
  62. 62. public class MyTestApplication extends MyApplication implements TestLifecycleApplication { @Override protected void init() { appComponent = DaggerAppTestComponent.create(); } }
  63. 63. protected void enqueueResponse(String filename) { String body = ResourceUtils.readFromFile(filename); MockResponse mockResponse = new MockResponse() .setBody(body) .setResponseCode(HttpURLConnection.HTTP_OK); mockWebServer.enqueue(mockResponse); }
  64. 64. @Override public void setup() throws Exception { super.setup(); controller = Robolectric .buildActivity(MockActivity.class) .create() .start() .resume() .visible(); fragment = DashboardDrivingModeFragment.newInstance(); controller.get().getSupportFragmentManager() .beginTransaction() .replace(R.id.container, fragment, null) .commit(); ButterKnife.bind(this, fragment.getView()); }
  65. 65. @Test public void testEmptyStateNotVisible() { enqueueResponse(“rest-currency-response.json”); btnCurrencyList.performClick(); assertThat(emptyView).isNotVisible(); }
  66. 66. THINGS TO REMEMBER • Orientation change
  67. 67. THINGS TO REMEMBER • Dagger2 is a powerful tool - make good use of it • Save yourselves from regression bugs
  68. 68. REFERENCES • http://antonioleiva.com/mvp-android/ • https://medium.com/@czyrux/presenter-surviving- orientation-changes-with- loaders-6da6d86ffbbf#.xou7c71uz • http://frogermcs.github.io/dependency-injection-with- dagger-2-custom-scopes/ • https://www.youtube.com/watch?v=oK_XtfXPkqw
  69. 69. Any questions? KRISTIJAN.JURKOVIC@INFINUM.CO @KJURKOVIC Visit infinum.co or find us on social networks: infinum.co infinumco infinumco infinum

×