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

Model-View-Intent

 Model-View-Intent

Andrdoid GDG Meetup 24th of Jannuary 2017

Hannes Dorfmann

January 24, 2017
Tweet

More Decks by Hannes Dorfmann

Other Decks in Programming

Transcript

  1. MODEL-VIEW-INTENT Hannes Dorfmann

  2. MVI MVP MVC MVVM MVC

  3. MVI MVP MVC MVVM

  4. WHAT IS A MODEL?

  5. class PersonsPresenter extends Presenter<PersonsView> { public void load(){ getView().showLoading(true); //

    Displays a ProgressBar backend.loadPersons(new Callback(){ public void onSuccess(List<Person> persons){ getView().showPersons(persons); // Displays a list of Persons } public void onError(Throwable error){ getView().showError(error); // Displays a error message }); } }
  6. class PersonsModel { private final boolean loading; private final List<Person>

    persons; private final Throwable error; public(boolean loading, List<Person> persons, Throwable error){ this.loading = loading; this.persons = persons; this.error = error; } // ... getters ... }
  7. class PersonsPresenter extends Presenter<PersonsView> { public void load(){ getView().render( new

    PersonsModel(true, null, null) ); backend.loadPersons(new Callback(){ public void onSuccess(List<Person> persons){ getView().render( new PersonsModel(false, persons, null) ); } public void onError(Throwable error){ getView().render( new PersonsModel(false, null, error) ); } }); } }
  8. MODEL-VIEW-INTENT

  9. ANDRÉ MEDEIROS (STALTZ) cycle.js http://cycle.js.org

  10. intent()

  11. model( intent() )

  12. view( model( intent() ) )

  13. view( model( intent() ) )

  14. None
  15. None
  16. public interface SearchView { Observable<String> searchIntent(); void render(SearchViewState viewState); }

  17. class SearchViewState { Throwable error; boolean loading; List<Product> result; }

  18. public class SearchFragment extends Fragment implements SearchView { ... @Override

    public Observable<String> searchIntent() { return RxSearchView.queryTextChanges(searchView) // Thanks Jake Wharton .filter(queryString -> queryString.length() > 3) .debounce(500, TimeUnit.MILLISECONDS); } }
  19. public class SearchFragment extends Fragment implements SearchView { ... @Override

    public void render(SearchViewState viewState) { if (viewState.isLoading()) { renderLoading(); } else if (viewState.getResult() != null) { renderResult(viewState.getResult()); } else if (viewState.getError() != null) { renderError(); } else { throw new IllegalArgumentException("Don't know how to render viewState"); } } }
  20. // Mosby 3.0 public class SearchPresenter extends MviBasePresenter<SearchView> { private

    final SearchInteractor searchInteractor; @Override protected void bindIntents() { Observable<SearchViewState> search = intent(SearchView::searchIntent) .flatMap(searchInteractor::search) subscribeViewState(search, SearchView::render); } }
  21. public class SearchInteractor { SearchEngine searchEngine; // Makes http calls

    public Observable<SearchViewState> search(String searchString) { return searchEngine.searchFor(searchString) // Observable<List<Product>> .map(products -> new SearchViewState(false, products, null)) .startWith(new SearchViewState(true, null, null)) .onErrorReturn(error -> new SearchViewState(false, null, error)); } }
  22. public class SearchInteractor { SearchEngine searchEngine; // Makes http calls

    public Observable<SearchViewState> search(String searchString) { return searchEngine.searchFor(searchString) // Observable<List<Product>> .map(products -> new SearchViewState(false, products, null)) .startWith(new SearchViewState(true, null, null)) .onErrorReturn(error -> new SearchViewState(false, null, error)); } }
  23. public class SearchInteractor { SearchEngine searchEngine; // Makes http calls

    public Observable<SearchViewState> search(String searchString) { return searchEngine.searchFor(searchString) // Observable<List<Product>> .map(products -> new SearchViewState(false, products, null)) .startWith(new SearchViewState(true, null, null)) .onErrorReturn(error -> new SearchViewState(false, null, error)); } }
  24. public class SearchInteractor { SearchEngine searchEngine; // Makes http calls

    public Observable<SearchViewState> search(String searchString) { return searchEngine.searchFor(searchString) // Observable<List<Product>> .map(products -> new SearchViewState(false, products, null)) .startWith(new SearchViewState(true, null, null)) .onErrorReturn(error -> new SearchViewState(false, null, error)); } }
  25. public class SearchInteractor { SearchEngine searchEngine; // Makes http calls

    public Observable<SearchViewState> search(String searchString) { return searchEngine.searchFor(searchString) // Observable<List<Product>> .map(products -> new SearchViewState(false, products, null)) .startWith(new SearchViewState(true, null, null)) .onErrorReturn(error -> new SearchViewState(false, null, error)); } }
  26. public class SearchInteractor { SearchEngine searchEngine; // Makes http calls

    public Observable<SearchViewState> search(String searchString) { return searchEngine.searchFor(searchString) // Observable<List<Product>> .map(products -> new SearchViewState(false, products, null)) .startWith(new SearchViewState(true, null, null)) .onErrorReturn(error -> new SearchViewState(false, null, error)); } }
  27. Search … searchIntent view.render( model )

  28. C searchIntent filter() C view.render( model )

  29. Ca searchIntent filter() C Ca view.render( model )

  30. Cam searchIntent Cam filter() C Ca view.render( model )

  31. Cam searchIntent filter() flatMap() view.render( model ) Cam C Ca

    Cam
  32. Cam searchIntent filter() flatMap() startWith( new Loading() ) Cam C

    Ca Cam
  33. Cam searchIntent filter() flatMap() startWith( new Loading() ) L view.render(

    SearchViewState.Loading) Cam C Ca Cam
  34. Cam searchIntent filter() flatMap() startWith( new Loading() ) L view.render(

    SearchViewState.Loading) Cam C Ca Cam
  35. Cam searchIntent filter() flatMap() startWith( new Loading() ) L view.render(

    SearchViewState.Loading) Cam C Ca Cam
  36. Cam searchIntent filter() flatMap() startWith( new Loading() ) L view.render(

    SearchViewState.Loading) Cam C Ca Cam
  37. Cam searchIntent filter() flatMap() startWith( new Loading() ) L view.render(

    SearchViewState.Loading) Cam C Ca Cam
  38. Cam searchIntent filter() flatMap() startWith( new Loading() ) L Cam

    C Ca Cam Cam
  39. Cam searchIntent filter() flatMap() startWith( new Loading() ) L Cam

    C Ca Cam Cam
  40. Cam searchIntent filter() flatMap() startWith( new Loading() ) L Cam

    C Ca Cam Cam
  41. Cam searchIntent filter() flatMap() startWith( new Loading() ) L Cam

    C Ca Cam Cam
  42. Cam searchIntent filter() flatMap() startWith( new Loading() ) L map(

    new SR() ) Cam C Ca Cam Cam
  43. Cam searchIntent filter() flatMap() startWith( new Loading() ) L map(

    new SR() ) Cam C Ca Cam Cam
  44. Cam searchIntent filter() flatMap() startWith( new Loading() ) L map(

    new SR() ) SR view.render( SearchViewState.SearchResult ) Cam C Ca Cam Cam
  45. Cam searchIntent filter() flatMap() startWith( new Loading() ) L map(

    new SR() ) SR view.render( SearchViewState.SearchResult ) Cam C Ca Cam Cam
  46. Cam searchIntent filter() flatMap() startWith( new Loading() ) L map(

    new SR() ) SR view.render( SearchViewState.SearchResult ) Cam C Ca Cam Cam
  47. None
  48. None
  49. public interface HomeView { Observable<Boolean> loadFirstPageIntent(); Observable<Boolean> loadNextPageIntent(); Observable<Boolean> pullToRefreshIntent();

    Observable<String> loadAllProductsFromCategoryIntent(); void render(HomeViewState viewState); }
  50. class HomePresenter extends MviBasePresenter<HomeView> { private final HomeFeedLoader feedLoader; @Override

    protected void bindIntents() { Observable<PartialState> firstPage = ... ; Observable<PartialState> pullToRefresh = ... ; Observable<PartialState> nextPage = ... ; Observable<PartialState> loadMoreFromCategory = ... ; Observable<PartialState> allIntents = Observable.merge(firstPage, pullToRefresh, nextPage, loadMoreFromCategory); Observable<HomeViewState> stateObservable = allIntents.scan(this::viewStateReducer) subscribeViewState(stateObservable, HomeView::render); }
  51. public HomeViewState reducer( HomeViewState previous, Foo foo ) { HomeViewState

    newState; // compute the new State // by taking previous state and foo into account return newState; }
  52. CONCLUSION • Model reflecting State • Unidirectional data flow •

    Pure Functions • Readable Code • Easy to test : assertEquals( expectedModel, model );
  53. THANKS hannesdorfmann.com github.com/sockeqwe/mosby