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

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

Hannes Dorfmann

June 16, 2016
Tweet

More Decks by Hannes Dorfmann

Other Decks in Programming

Transcript

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

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

    change” Book 
 currentPage() nextPage() print() Book 
 currentPage() nextPage() Printer 
 print(Book)
  3. AUTOVALUE @AutoValue public abstract class HotDog implements Parcelable {
 @Json

    public abstract double getPrice(); @ColumnName public abstract boolean hasKetchup(); }
  4. 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); } }
  5. PLAID • https://github.com/nickbutcher/plaid • 5170 Stars on Github • Last

    commit: 1st of June 2016 • 5.000 - 10.000 installs 2nd June 2016
  6. PLAID • No clear Separation of responsibilities • AsyncTask •

    Android Services • Shared Preferences for persistency 2nd June 2016
  7. 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
  8. 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 }
  9. CLEAN ARCHITECTURE Entity Entity Entity Interactor
 Presenter
 View
 Break the

    Rules! https://publicobject.com/2016/04/20/deliberate-disobedience/ Jesse Wilson
  10. interface SourceDao {
 
 fun getAllSources(): Observable<List<Source>>
 
 fun getEnabledSources():

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

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

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

    source = Source.create(…) val inserted = dao.insert(source)
 .toBlocking().first() assertEquals(1, insertRes) } }
  14. class SqliteSourceDao : SourceDao, Dao() {
 
 fun insert(source: Source):

    Observable<Long> = insert(TABLE, source.toContentValues()) }
  15. 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)) } }
  16. 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) }
  17. TESTING MVP • View with Mock Presenter • Presenter with

    Mock View and Mock Model • Model plain old unit test (slides before)