Refactoring Plaid - A Reactive MVP Approach

Refactoring Plaid - A Reactive MVP Approach

Nick Butcher, developer advocate at Google, has open sourced an android app called Plaid with an outstanding UI, meaningful animations and a lot of others material design goodies.

However, from the software architecture’s point of view, the architecture of this app more “traditional” so that both, beginners and expert developers, can understand the source code easily. Unfortunately, that means that there is a lack of separation of concerns that modern software architectures offers. This talk discusses how to improve the architecture of this app by applying Model-View-Presenter (MVP) to create modular and decoupled components that are easy to test. Furthermore, this talk shows how to improve the code quality by using well known libraries like RxJava (foreknowledge is not a strong requirement), dependency injection and how to test such an App by doing Test-Driven-Development (TDD) from the very beginning.

The aim of this talk is to showcase the importance of a well thought out software architecture and how to implement such an MVP based architecture and last but not least to clarify what the word “reactive” actually means in this context.

YouTube Video: https://youtu.be/wWyPc_HN77c

63c8b0590595c3de58406678ef99a6bf?s=128

Hannes Dorfmann

June 16, 2016
Tweet

Transcript

  1. None
  2. None
  3. HELLO I’m Hannes

  4. REFACTORING PLAID A REACTIVE MVP APPROACH

  5. SLOWED DOWN BY BAD CODE?

  6. FEAR CHANGING CODE!

  7. FEAR CHANGING CODE!

  8. TDD Test-Driven-Development

  9. SOLID Robert C. Martins
 „Uncle Bob“

  10. SINGLE RESPONSIBILITY “A class should only have one reason to

    change” Book 
 currentPage() nextPage() print()
  11. SINGLE RESPONSIBILITY “A class should only have one reason to

    change” Book 
 currentPage() nextPage() print() Book 
 currentPage() nextPage() Printer 
 print(Book)
  12. IMMUTABILITY

  13. None
  14. You want an 
 immutable hot dog

  15. AUTOVALUE @AutoValue public abstract class HotDog { }

  16. AUTOVALUE @AutoValue public abstract class HotDog implements Parcelable { }

  17. AUTOVALUE @AutoValue public abstract class HotDog implements Parcelable {
 @Json

    public abstract double getPrice(); }
  18. AUTOVALUE @AutoValue public abstract class HotDog implements Parcelable {
 @Json

    public abstract double getPrice(); @ColumnName public abstract boolean hasKetchup(); }
  19. AUTOVALUE @AutoValue public abstract class HotDog implements Parcelable {
 @Json

    public abstract double getPrice(); @ColumnName public abstract boolean hasKetchup(); public abstract static HotDog(double p, boolean k) { return new AutoValue_HotDog(p, k); } }
  20. MVC Model-View-Controller

  21. MVC Trygve Reenskaug View Model Controller observes changes

  22. None
  23. PLAID Nick Butcher

  24. None
  25. PLAID • https://github.com/nickbutcher/plaid • 5170 Stars on Github • Last

    commit: 1st of June 2016 • 5.000 - 10.000 installs 2nd June 2016
  26. None
  27. None
  28. None
  29. None
  30. None
  31. None
  32. PLAID • No clear Separation of responsibilities • AsyncTask •

    Android Services • Shared Preferences for persistency 2nd June 2016
  33. None
  34. None
  35. None
  36. None
  37. HomeActivity

  38. HomeActivity

  39. HomeActivity ConnectivityManager

  40. HomeActivity ConnectivityManager Animations

  41. HomeActivity ConnectivityManager Animations FeedAdapter

  42. HomeActivity ConnectivityManager Animations FilterAdapter FeedAdapter

  43. HomeActivity ConnectivityManager Animations onActivityResult() FilterAdapter FeedAdapter

  44. HomeActivity ConnectivityManager Animations onActivityResult() FilterAdapter FeedAdapter

  45. HomeActivity ConnectivityManager Animations onActivityResult() FilterAdapter FeedAdapter DataManager pageIndexMap runningCallsMap

  46. HomeActivity ConnectivityManager Animations onActivityResult() FilterAdapter FeedAdapter DataManager pageIndexMap runningCallsMap FilterAdapter

    getFilters() registerFilterChanged()
  47. HomeActivity ConnectivityManager Animations onActivityResult() FilterAdapter FeedAdapter DataManager pageIndexMap runningCallsMap FilterAdapter

    getFilters() registerFilterChanged()
  48. HomeActivity ConnectivityManager Animations onActivityResult() FilterAdapter FeedAdapter DataManager pageIndexMap runningCallsMap FilterAdapter

    getFilters() registerFilterChanged()
  49. HomeActivity ConnectivityManager Animations onActivityResult() FilterAdapter FeedAdapter DataManager pageIndexMap runningCallsMap FilterAdapter

    getFilters() registerFilterChanged()
  50. HomeActivity ConnectivityManager Animations onActivityResult() FilterAdapter FeedAdapter DataManager pageIndexMap runningCallsMap FilterAdapter

    getFilters() registerFilterChanged()
  51. HomeActivity ConnectivityManager Animations onActivityResult() FilterAdapter FeedAdapter DataManager pageIndexMap runningCallsMap FilterAdapter

    getFilters() registerFilterChanged() SourceManager
  52. HomeActivity ConnectivityManager Animations onActivityResult() FilterAdapter FeedAdapter DataManager pageIndexMap runningCallsMap FilterAdapter

    getFilters() registerFilterChanged() SourceManager
  53. HomeActivity ConnectivityManager Animations onActivityResult() FilterAdapter FeedAdapter DataManager pageIndexMap runningCallsMap FilterAdapter

    getFilters() registerFilterChanged() SourceManager
  54. REFACTORING

  55. Internet

  56. Mock

  57. https://www.getpostman.com/

  58. src/test/resources ClassLoader classLoader = getClass().getClassLoader(); File file = new File(classLoader.getResource("somefile.json").getFile());

  59. src/test/resources ClassLoader classLoader = getClass().getClassLoader(); File file = new File(classLoader.getResource("somefile.json").getFile());

    Mock https://github.com/square/okhttp/tree/master/mockwebserver
  60. src/test/resources ClassLoader classLoader = getClass().getClassLoader(); File file = new File(classLoader.getResource("somefile.json").getFile());

    Mock https://github.com/square/okhttp/tree/master/mockwebserver @Rule https://github.com/artem-zinnatullin/qualitymatters
  61. class HomeActivityTest {
 @Rule MockWebServer server = new MockWebServer(); @Test

    public void showItems(){ server.enqueue( new MockResponse (…) ); onView( withId( R.id.recyclerView )) .check( matches( isDisplayed() ); onView(withId( R.id.recyclerView)) .check( new RecyclerViewItemCountAssertion(20) ) // test visibility of error and loading view }
  62. CLEAN ARCHITECTURE Entity Entity Entity Interactor
 Presenter
 View


  63. CLEAN ARCHITECTURE Entity Entity Entity Interactor
 Presenter
 View
 Break the

    Rules! https://publicobject.com/2016/04/20/deliberate-disobedience/ Jesse Wilson
  64. HomeActivity ConnectivityManager Animations onActivityResult() FilterAdapter FeedAdapter DataManager pageIndexMap runningCallsMap FilterAdapter

    getFilters() registerFilterChanged() SourceManager
  65. HomeActivity Animations FilterAdapter FeedAdapter DataManager pageIndexMap runningCallsMap SourceManager

  66. REACTIVE PROGRAMMING

  67. java.util.Observable

  68. SQLBrite SQLiteDatabase SourceDao

  69. interface SourceDao {
 
 fun getAllSources(): Observable<List<Source>>
 
 fun getEnabledSources():

    Observable<List<Source>>
 
 fun insert(source: Source): Observable<Long>
 
 fun delete(source: Source): Observable<Int> }
  70. @AutoValue class Source : Parcelable {
 @ColumnName(COL_NAME) fun name() :

    String @ColumnName(COL_ENABLED) fun boolean enabled(); }
  71. TDD • Write failing unit test first • Write just

    enough code to pass all tests • Repeat
  72. class SqliteSourceDaoTest {
 @Test fun insertAndGet(){ dao = SqliteSourceDao() val

    source = Source.create(…) val inserted = dao.insert(source)
 .toBlocking().first() assertEquals(1, insertRes) } }
  73. Unit Test failed

  74. class SqliteSourceDao : SourceDao, Dao() {
 
 fun insert(source: Source):

    Observable<Long> = insert(TABLE, source.toContentValues()) }
  75. Unit Tests OK

  76. class SqliteSourceDaoTest {
 @Test fun insertAndGet(){ dao = SqliteSourceDao() val

    source = Source.create(…) val inserted = dao.insert(source)
 .toBlocking().first() assertEquals(1, insertRes) val queried = dao.getAllSources().toBlocking() assertEquals(queried, listOf(source)) } }
  77. Unit Test failed

  78. class SqliteSourceDao : SourceDao, Dao() {
 
 fun insert(source: Source):

    Observable<Long> = insert(TABLE, source.toContentValues()) fun getAll(source: Source): Observable<List<Source>> = query( SELECT(“*“).FROM(TABLE) ) .mapToList(Source.MAPPER) }
  79. Unit Tests OK

  80. HomeActivity Animations FilterAdapter FeedAdapter DataManager pageIndexMap runningCallsMap SourceManager

  81. HomeActivity Animations FilterAdapter FeedAdapter DataManager pageIndexMap runningCallsMap SourceDao

  82. HomeActivity Animations FilterAdapter FeedAdapter DataManager pageIndexMap runningCallsMap SourceDao

  83. HomeActivity Animations FilterAdapter FeedAdapter DataManager pageIndexMap runningCallsMap SourceDao

  84. None
  85. None
  86. None
  87. RxBus

  88. RxBus

  89. HomeActivity Animations FilterAdapter FeedAdapter DataManager SourceDao P R E
 S


    E
 N
 T
 E R
 View Model
  90. TESTING MVP • View with Mock Presenter • Presenter with

    Mock View and Mock Model • Model plain old unit test (slides before)
  91. SINGLE RESPONSIBILITY DataManager pageIndexMap runningCallsMap SourceDao

  92. SINGLE RESPONSIBILITY DataManager pageIndexMap runningCallsMap SourceDao Route

  93. SINGLE RESPONSIBILITY DataManager pageIndexMap runningCallsMap SourceDao Route Queries Http Endpoint

  94. SINGLE RESPONSIBILITY DataManager pageIndexMap runningCallsMap SourceDao Route Router Queries Http

    Endpoint
  95. SINGLE RESPONSIBILITY DataManager pageIndexMap runningCallsMap SourceDao Route Router Queries Http

    Endpoint A collection of Routes
  96. SINGLE RESPONSIBILITY DataManager pageIndexMap runningCallsMap SourceDao Route Router Page Queries

    Http Endpoint A collection of Routes
  97. SINGLE RESPONSIBILITY DataManager pageIndexMap runningCallsMap SourceDao Route Router Page Queries

    Http Endpoint A collection of Routes Page Index
  98. OBSERVABLE STREAM SourceDao Route Router Page SQLBrite SQLiteDatabase

  99. OBSERVABLE STREAM SourceDao Route Router Page SQLBrite SQLiteDatabase

  100. SourceDao Route Router Page SQLBrite SQLiteDatabase SourceDao HomePresenter HomeActivity FilterPresenter

    FilterFragment
  101. SourceDao Route Router Page SQLBrite SQLiteDatabase SourceDao HomePresenter HomeActivity FilterPresenter

    FilterFragment
  102. SourceDao Route Router Page SQLBrite SQLiteDatabase SourceDao HomePresenter HomeActivity FilterPresenter

    FilterFragment
  103. SourceDao Route Router Page SQLBrite SQLiteDatabase SourceDao HomePresenter HomeActivity FilterPresenter

    FilterFragment
  104. SourceDao Route Router Page SQLBrite SQLiteDatabase SourceDao HomePresenter HomeActivity FilterPresenter

    FilterFragment
  105. SourceDao Route Router Page SQLBrite SQLiteDatabase SourceDao HomePresenter HomeActivity FilterPresenter

    FilterFragment
  106. SourceDao Route Router Page SQLBrite SQLiteDatabase SourceDao HomePresenter HomeActivity FilterPresenter

    FilterFragment
  107. SourceDao Route Router Page SQLBrite SQLiteDatabase SourceDao HomePresenter HomeActivity FilterPresenter

    FilterFragment
  108. SourceDao Route Router Page SQLBrite SQLiteDatabase SourceDao HomePresenter HomeActivity FilterPresenter

    FilterFragment
  109. SourceDao Route Router Page SQLBrite SQLiteDatabase SourceDao HomePresenter HomeActivity FilterPresenter

    FilterFragment
  110. SourceDao Route Router Page SQLBrite SQLiteDatabase SourceDao HomePresenter HomeActivity FilterPresenter

    FilterFragment
  111. SourceDao Route Router Page SQLBrite SQLiteDatabase SourceDao HomePresenter HomeActivity FilterPresenter

    FilterFragment
  112. SourceDao Route Router Page SQLBrite SQLiteDatabase SourceDao HomePresenter HomeActivity FilterPresenter

    FilterFragment
  113. SourceDao Route Router Page SQLBrite SQLiteDatabase SourceDao HomePresenter HomeActivity FilterPresenter

    FilterFragment
  114. SourceDao Route Router Page SQLBrite SQLiteDatabase SourceDao HomePresenter HomeActivity FilterPresenter

    FilterFragment
  115. SourceDao Route Router Page SQLBrite SQLiteDatabase SourceDao HomePresenter HomeActivity FilterPresenter

    FilterFragment
  116. SourceDao Route Router Page SQLBrite SQLiteDatabase SourceDao HomePresenter HomeActivity FilterPresenter

    FilterFragment
  117. UNIDIRECTIONAL DATAFLOW

  118. None
  119. HomePresenter HomeActivity FilterPresenter FilterFragment SearchPresenter SearchActivity SettingsPresenter SettingsActivity

  120. None
  121. MOVE FAST WITHOUT FEARING TO BREAK THINGS

  122. THE CODE YOU WRITE TODAY WILL BE LEGACY CODE TOMORROW

  123. JUST LIKE TODAY’S CELLS IN YOUR BODY ARE LEGACY CELLS

    TOMORROW
  124. We are hiring! www.tickaroo.com

  125. QUESTIONS? hannesdorfmann.com @sockeqwe