Talk about what MVP means to different people and how using MVP doesn't even matter. It's about the discussion.
I've hacked an app together with MVP, I want to show my findings and opinions about the most over defined term for a pattern ever.
2. My Vision Program
- Highlight the variations
- Talk about the conflictions
- Define the naming
- Example implementation
- Emphasize important points
- Get Pragmatic
10. 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/
12. image
Indecent Exposure (42):
This smell indicates the lack of
what David Parnas so famously
termed information hiding. The
smell occurs when methods or
classes that ought
not to be visible to clients are
publicly visible to them. Exposing
such code means that
clients know about code that is
unimportant or only indirectly
important. This contributes
to the complexity of a design.
16. 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.
17. 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
18. 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
19. 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
21. Package Structure
- Not necessary for MVP but a
good bedrock to work from
- Feature based
- Clearly see the app’s
concerns
- Minimal learning curve
22. Core / Mobile split
MVP 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.html
https://www.novoda.com/blog/hexawhat-architecture/
23. Single Feature Example - Comments
Only the Activity needs to be public
First place to start [Feature]Mvp.java
Classes shaded out not directly involved
in MVP
24. [Feature]Mvp.java
class 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
25. CommentsMvp.java Implementers
class 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 {
...
}
26. CommentsMvp.Model
public 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
27. CommentsMvp.Model Implementor
class 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
28. CommentsMvp.Model Implementor
class 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
29. CommentsMvp.Model Implementor
class 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
30. CommentsMvp.View
public 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”
31. CommentsMvp.Presenter
public 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
32. CommentsMvp.Presenter Implementor
class 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;
…
@Override
public 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
33. CommentsMvp.Presenter Implementor
class 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;
…
@Override
public 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
34. CommentsMvp.Presenter Implementor
class 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;
…
@Override
public 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
35. CommentsMvp.Presenter Implementor
class 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;
…
@Override
public 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
36. CommentsMvp.Presenter Implementor
…
@Override
public void onSelectedFilterCommentByNewestFirst() {
model.loadCommentsFor(cure, SortOrder.NEWEST_FIRST, this);
}
...
@Override
public 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
37. ViewComment
A 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
38. 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);
}
}
39. Presenter Instantiation
onCreate is in the Activity, newInstance is in the Presenter (or in any factory)
@Override
protected 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));
}
40. 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 widgets
public class DetailsActivityView extends AppCompatActivity implements DetailsMvp.View {
private DetailsMvp.Presenter presenter;
private TextView titleWidget;
private TextView descriptionWidget;
private RatingBar ratingBarWidget;
private AdView advertWidget;
I've hacked an app together with MVP, I want to show my findings and opinions about the most over defined term for a pattern ever.
https://play.google.com/store/apps/details?id=com.blundell.hangovercures.free
So you know MVP?
You have read all of these right
And you can explain MVP with a simple diagram or sentence right
Is this the answer?
Seems like everybody else thinks they can as well, this is a google images search on the subject.
Lots of diagrams all showing .. something different
Recommended if you want to get into diagrams. I have another H&T on the C4 model coming soon!
Everyone has their own opinion and choice of what the pattern is, because it’s so over defined.
Everyone says MVP but nobody talks about the details.
Yes it means you have a front end architecture with some separation, but it doesn’t mean two people would code it the same way.
Change from “This should not be in the view” to “This View does not have a single responsibility anymore, who normally controls X?”
Beware the ivory tower syndrome
everyone thinks they are an architect and that their way is the right way
http://techdistrict.kirkk.com/2009/11/03/turtles-and-architecture/
Need to make sure when you are selecting patterns, you are refactoring for a reason
In this book he doesn’t just say, here’s a cool pattern use it! He deliberately lays out the positives and the negatives
Try to talk about code smells when you are tackling problems.
“MVP will help with fixing the ‘hollywood principle’ here”
Always give a reference when needed
The MVP of choice doesn’t matter too much (on a single project) as long as the code is SOLID, crafted and at times pragmatic
If we are going to synchronise and agree we need a common language and shared definitions
Since this is a talk and not a discussion unfortunately you just have to sit there and listen
This is not perfect! And I don’t want you to concentrate on the things that are missing, or what you can improve or add.
Just listen about what is there, as I think it has some “clean”ness to it.
I have already done a H&T that was recorded on the “core mobile divide” you should go watch
The inner classes allow for great naming practices and readabilty.
CommentsModel implements CommentsMvp.Model
Activity implements CommentsMvp.View
CommentsPresenter implements CommentsMvp.Presenter
This stops strange things like CommentsPresenterImpl implements CommentsPresenter
It also highlights the layer pattern very obviously to the legacy developer
More on the AnalyticsView later
Think of the Activity in two parts.
It is the Mvp.View
It’s onCreate is the static void main method that creates the MVP triad
Serious Time
It doesn’t matter who knows the most or what pattern we use.
What matters is we work together
And we get shit done
We don’t need 1 architecture to bind them
We need a multiple options, a bag full of tools with descriptions of benefits and drawbacks, then new projects can pick from the selection.
The skill in architecture is knowing what you need for your use case and knowing how to apply that