Upload
paul-blundell
View
491
Download
0
Embed Size (px)
Citation preview
Most Volatile Palaver
Paul BlundellMVP examined holistically and subjectively
My Vision Program- Highlight the variations- Talk about the conflictions- Define the naming- Example implementation- Emphasize important points- Get Pragmatic
image
Oh you know MVP?
Oh you know MVP?http://www.wildcrest.com/Potel/Portfolio/mvp.pdf http://martinfowler.com/eaaDev/uiArchs.html https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter https://msdn.microsoft.com/en-us/library/ff649571.aspx https://github.com/konmik/konmik.github.io/wiki/Introduction-to-Model-View-Presenter-on-Android http://c2.com/cgi/wiki?ModelViewPresenter http://www.gwtproject.org/articles/mvp-architecture.html https://www.youtube.com/watch?v=oiNfPjV72lg http://polymorphicpodcast.com/shows/mv-patterns/ https://developer.ibm.com/open/2015/10/22/model-view-presenter-mvp-for-ibm-ready-apps/ https://caster.io/episodes/episode-48-model-view-presenter-part-1-what-is-the-mvp-pattern/ http://fernandocejas.com/tag/model-view-presenter/ http://engineering.remind.com/android-code-that-scales/ http://antonioleiva.com/mvp-android/ http://geekswithblogs.net/nharrison/archive/2008/09/22/125373.aspx
image
image
PV
image
image
https://leanpub.com/software-architecture-for-developers https://leanpub.com/visualising-software-architecture
image
I know MVP
No I know MVP
No. I know MVP
Ivory Tower Syndrome- My MVP works so it should work
everywhere- My MVP is the only possible
architecture- Non collaboration- Lack of discussion of the details
further reading: http://techdistrict.kirkk.com/2009/11/03/turtles-and-architecture/
image
image
Indecent Exposure (42):
This smell indicates the lack of what David Parnas so famouslytermed information hiding. The smell occurs when methods or classes that oughtnot to be visible to clients are publicly visible to them. Exposing such code means thatclients know about code that is unimportant or only indirectly important. This contributesto the complexity of a design.
image
image
image
Here is another opinion!- Hangover Cures is an app I wrote back in 2012- Two screens, a list of hangover cures, individual
details- All code was in the Activity- Added new feature:Now has a comment
stream, for people to leave comments about the cures.
Model- Thin layer and a boundary to the rest of the
architecture of the application (in my case ‘clean architecture’)
- It’s not so much a domain model more of a ‘what can be done on this view’ api
- Model contains the threading choices
View- Passive View (not observing the model)- Gets information from ViewModel objects- The Activity as the View on Android- Treat Android Views like primitives and we
always want to wrap them in a Domain View- Encapsulate native Android user interaction
mechanisms and define Domain specific ones
Presenter- Has knowledge of Model & View- Decides on action after user interaction- Observes the Model for changes- Decides how View is notified of Model changes- Doesn’t care about threading- Never uses primitive Views or native Listeners
image
Package Structure- Not necessary for MVP but
a good bedrock to work from
- Feature based- Clearly see the app’s
concerns- Minimal learning curve
Core / Mobile splitMVP is a UI level architecture it is not an application
architecture
MVP belongs in the mobile module*
The presenter can have Android imports, that’s ok
For application wide architecture see: https://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.htmlhttps://www.novoda.com/blog/hexawhat-architecture/
*controversial
Single Feature Example - CommentsOnly the Activity needs to be public
First place to start [Feature]Mvp.java
Classes shaded out not directly involved in MVP
[Feature]Mvp.javaclass CommentsMvp {
public interface Model extends Closeable {
...
}
public interface View {
...
}
public interface Presenter {
...
}
- The inner classes allow for great naming practices and improved readabilty.
- This stops strange things like CommentsPresenterImpl implements
CommentsPresenter
- It also highlights the UI layers MVP pattern very obviously to the legacy developer
CommentsMvp.java Implementersclass CommentsModel implements CommentsMvp.Model {
...}
public class CommentsActivityView extends AppCompatActivity implements CommentsMvp.View, CommentsStream.Listener {...
}
class CommentsAnalyticsView implements CommentsMvp.View {...
}
class CommentsPresenter implements CommentsMvp.Presenter, CommentsMvp.Model.AdvertRequestLoadedCallback, CommentsMvp.Model.UserLoadedCallback, CommentsMvp.Model.CommentSavedCallback, CommentsMvp.Model.CommentsLoadedCallback, CommentsMvp.Model.CommentUpdatedCallback {
...}
CommentsMvp.Modelpublic interface Model extends Closeable {
void loadUser(UserLoadedCallback callback); void loadAdvertRequest(AdvertRequestLoadedCallback callback); void loadCommentsFor(Cure cure, SortOrder sortOrder, CommentsLoadedCallback callback);
void saveComment(Cure.Id id, ViewComment comment, CommentSavedCallback callback); void saveVoteFor(Cure.Id id, ViewComment comment, Vote down, CommentUpdatedCallback callback); boolean canVoteOnCommentWith(Cure.Id cureId, Comment.Id commentId);
interface CommentSavedCallback { void onSaved(ViewComment comment); } interface UserLoadedCallback { void onLoaded(String username); // TODO param can be an object containing also users profile pic } interface AdvertRequestLoadedCallback { void onLoaded(AdRequest advertRequest); } interface CommentsLoadedCallback { void onLoaded(List<ViewComment> comments); } interface CommentUpdatedCallback { void onUpdated(ViewComment comment); }}
- callbacks here, can be any observing method
- save/load is a simple pattern when you don’t have much client side state
- knowledge of MVP domain objects
- no knowledge of Android
CommentsMvp.Model Implementorclass CommentsModel implements CommentsMvp.Model { ...
private final UseCaseModelAdapter modelAdapter; private final Log log;
public CommentsModel(UseCaseModelAdapter modelAdapter, Log log) { this.modelAdapter = modelAdapter; this.log = log; }
@Override public void loadAdvertRequest(final AdvertRequestLoadedCallback callback) { Subscription subscription = Observable.create(modelAdapter.getAdvertRequest()) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread())
...
}
- Thin layer that then moves from MVP architecture to our back end of the app architecture
- Injects the logger to allow dependency inversion
- Controls asynchronicity as the model knows when its innards need threads
CommentsMvp.Model Implementorclass CommentsModel implements CommentsMvp.Model { ...
private final UseCaseModelAdapter modelAdapter; private final Log log;
public CommentsModel(UseCaseModelAdapter modelAdapter, Log log) { this.modelAdapter = modelAdapter; this.log = log; }
@Override public void loadAdvertRequest(final AdvertRequestLoadedCallback callback) { Subscription subscription = Observable.create(modelAdapter.getAdvertRequest()) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread())
...
}
- Thin layer that then moves from MVP architecture to our back end of the app architecture
- Injects the logger to allow dependency inversion
- Controls asynchronicity as the model knows when its innards need threads
CommentsMvp.Model Implementorclass CommentsModel implements CommentsMvp.Model { ...
private final UseCaseModelAdapter modelAdapter; private final Log log;
public CommentsModel(UseCaseModelAdapter modelAdapter, Log log) { this.modelAdapter = modelAdapter; this.log = log; }
@Override public void loadAdvertRequest(final AdvertRequestLoadedCallback callback) { Subscription subscription = Observable.create(modelAdapter.getAdvertRequest()) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread())
...
}
- Thin layer that then moves from MVP architecture to our back end of the app architecture
- Injects the logger to allow dependency inversion
- Controls asynchronicity as the model knows when its innards need threads
CommentsMvp.Viewpublic interface View { void create();
void show(ViewCure cure);
void show(String username);
void show(AdRequest advertRequest);
void show(List<ViewComment> comments);
void showInteractionsFor(ViewComment comment);
void update(ViewComment comment);
void notifyAlreadyVoted();}
- All commanding methods, telling what to do
- Use ViewModels to pass the data, here the naming I think lets it down a bit as ViewCure could be read incorrectly as “view the cure” rather than “the view cure model”
CommentsMvp.Presenterpublic interface Presenter { void onCreate(ViewCure cure);
void onCommented(String comment);
void onSelected(ViewComment comment);
void onSelectedVoteUp(ViewComment comment);
void onSelectedVoteDown(ViewComment comment);
void onSelectedFilterCommentsByMostPopularFirst();
void onSelectedFilterCommentByNewestFirst();}
- includes the lifecycle methods needed
- includes methods that react to user interaction (i.e. methods you call inside a click listener)
Want to investigate this for the lifecycle callbacks https://github.com/soundcloud/lightcycle
CommentsMvp.Presenter Implementorclass CommentsPresenter implements CommentsMvp.Presenter, CommentsMvp.Model.AdvertRequestLoadedCallback, CommentsMvp.Model.UserLoadedCallback, CommentsMvp.Model.CommentSavedCallback, CommentsMvp.Model.CommentsLoadedCallback, CommentsMvp.Model.CommentUpdatedCallback {
private final CommentsMvp.Model model; private final CommentsMvp.View view;
private String username; private ViewCure cure;
…
@Overridepublic void onCreate(ViewCure cure) { this.cure = cure; view.create(); view.show(cure); model.loadAdvertRequest(this); model.loadUser(this); model.loadCommentsFor(cure, SortOrder.MOST_POPULAR_FIRST, this);}
- observes the model
- knows about the Model and the View
- keeps state
- onCreate it tells the view to create, populates the view and requests first load from the model
CommentsMvp.Presenter Implementorclass CommentsPresenter implements CommentsMvp.Presenter, CommentsMvp.Model.AdvertRequestLoadedCallback, CommentsMvp.Model.UserLoadedCallback, CommentsMvp.Model.CommentSavedCallback, CommentsMvp.Model.CommentsLoadedCallback, CommentsMvp.Model.CommentUpdatedCallback {
private final CommentsMvp.Model model; private final CommentsMvp.View view;
private String username; private ViewCure cure;
…
@Overridepublic void onCreate(ViewCure cure) { this.cure = cure; view.create(); view.show(cure); model.loadAdvertRequest(this); model.loadUser(this); model.loadCommentsFor(cure, SortOrder.MOST_POPULAR_FIRST, this);}
- observes the model
- knows about the Model and the View
- keeps state
- tells the view to create, populates the view and requests first load from the model
CommentsMvp.Presenter Implementorclass CommentsPresenter implements CommentsMvp.Presenter, CommentsMvp.Model.AdvertRequestLoadedCallback, CommentsMvp.Model.UserLoadedCallback, CommentsMvp.Model.CommentSavedCallback, CommentsMvp.Model.CommentsLoadedCallback, CommentsMvp.Model.CommentUpdatedCallback {
private final CommentsMvp.Model model; private final CommentsMvp.View view;
private String username; private ViewCure cure;
…
@Overridepublic void onCreate(ViewCure cure) { this.cure = cure; view.create(); view.show(cure); model.loadAdvertRequest(this); model.loadUser(this); model.loadCommentsFor(cure, SortOrder.MOST_POPULAR_FIRST, this);}
- observes the model
- knows about the Model and the View
- keeps state
- tells the view to create, populates the view and requests first load from the model
CommentsMvp.Presenter Implementorclass CommentsPresenter implements CommentsMvp.Presenter, CommentsMvp.Model.AdvertRequestLoadedCallback, CommentsMvp.Model.UserLoadedCallback, CommentsMvp.Model.CommentSavedCallback, CommentsMvp.Model.CommentsLoadedCallback, CommentsMvp.Model.CommentUpdatedCallback {
private final CommentsMvp.Model model; private final CommentsMvp.View view;
private String username; private ViewCure cure;
…
@Overridepublic void onCreate(ViewCure cure) { this.cure = cure; view.create(); view.show(cure); model.loadAdvertRequest(this); model.loadUser(this); model.loadCommentsFor(cure, SortOrder.MOST_POPULAR_FIRST, this);}
- observes the model
- knows about the Model and the View
- keeps state
- tells the view to create, populates the view and requests first load from the model
CommentsMvp.Presenter Implementor…
@Overridepublic void onSelectedFilterCommentByNewestFirst() { model.loadCommentsFor(cure, SortOrder.NEWEST_FIRST, this);}
...
@Overridepublic void onSaved(ViewComment comment) { view.update(comment);}
...
- observes the View and decides what action to take on user interaction
- observes the Model and decides how the view should be updated
ViewCommentA comment whos responsibilities lie in the view layer
- vs a comment whos responsibilities lie in the domain layer
- Meaning it can have view centric methods- getContentDescription, getFormattedDate
- Immutable, only get never set - Could implement Parcelable
CommentsAnalyticsView- Analytics tracking can be
thought of as another view on the same presenter
- Uses decorator pattern, could also be an observer pattern if you wanted more than two
- Experimental ...
class CommentsAnalyticsView implements CommentsMvp.View { ...
public CommentsAnalyticsView(Tracker tracker, CommentsMvp.View view) { this.tracker = tracker; this.view = view; }
@Override public void create() { tracker.setScreenName("Comments"); tracker.send(new HitBuilders.ScreenViewBuilder().build()); view.create(); }
@Override public void show(Cure cure) { view.show(cure); }… @Override public void showInteractionsFor(ViewComment comment) { tracker.setScreenName("Interaction " + comment.getName()); tracker.send(new HitBuilders.ScreenViewBuilder().build()); view.showInteractionsFor(comment); }}
Presenter InstantiationonCreate is in the Activity, newInstance is in the Presenter (or in any factory)
@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); ViewCure cure = (ViewCure) getIntent().getSerializableExtra(EXTRA_CURE); HangoverCuresApplication dependencyProvider = (HangoverCuresApplication) getApplicationContext(); presenter = CommentsPresenter.newInstance(dependencyProvider, this); presenter.onCreate(cure);}
public static CommentsMvp.Presenter newInstance(HangoverCuresApplication dependencyProvider, CommentsMvp.View view) { Log log = new AndroidLog(); FirebaseCommentRepository firebase = new FirebaseCommentRepository(); SharedPrefsVoteRepository sharedPrefsVoteRepository = SharedPrefsVoteRepository.newInstance(dependencyProvider.getApplicationContext()); CommentUseCase commentUseCase = new CommentUseCase(firebase); VotingUseCase votingUseCase = new VotingUseCase(sharedPrefsVoteRepository); UseCaseModelAdapter useCaseModelAdapter = new UseCaseModelAdapter(commentUseCase, votingUseCase); CommentsMvp.Model model = new CommentsModel(useCaseModelAdapter, log); Tracker tracker = dependencyProvider.getAnalyticsTracker(); return new CommentsPresenter(model, new CommentsAnalyticsView(tracker, view));}
Deliberate View language- Try to consider the domain of your UI (talk to the designers &
business analysts)- Never end a custom view with the word view- Avoids communication issues & complications
class CommentsStream extends LinearLayout {
- When using primitive views consider naming them widgetspublic class DetailsActivityView extends AppCompatActivity implements DetailsMvp.View {
private DetailsMvp.Presenter presenter;
private TextView titleWidget;
private TextView descriptionWidget;
private RatingBar ratingBarWidget;
private AdView advertWidget;
image
image
SRS TEAM TIME
image
image
image
image
https://github.com/novoda/spikes/tree/master/Architecture
image
Thanksblundell_appsG what?blundell
throw new SlideIndexOutOfBoundsException();
blundell_appsG what?blundell
Questions?