I am migrating my apps to MVP. Have taken hints on a static presenter pattern from this konmik
This is my brief MVP strategy. Removed most of the boilerplate and MVP listeners for brevity. This strategy has helped me orientation change proof my background processes. The activity correctly recovers from a normal pause compared to pause which is finishing the activity. Also the Presenter only has application context so it does not hold onto activity context.
I am not a java expert and this is my first foray into MVP and using a static presenter has made me uncomfortable. Am I missing something? My app is working fine and has become much more responsive.
View
public class MainActivity extends Activity{
private static Presenter presenter;
protected void onResume() {
if (presenter == null)
presenter = new Presenter(this.getApplicationContext());
presenter.onSetView(this);
presenter.onResume();
}
protected void onPause() {
presenter.onSetView(null);
if(isFinishing())presenter.onPause();
}
}
Presenter
public class Presenter {
private MainActivity view;
Context context;
public Model model;
public Presenter(Context context) {
this.context = context;
model = new Model(context);
}
public void onSetView(MainActivity view) {
this.view = view;
}
public void onResume(){
model.resume();
}
public void onPause(){
model.pause();
}
}
Model
public class Model {
public Model(Context context){
this.context = context;
}
public void resume(){
//start data acquisition HandlerThreads
}
public void pause(){
//stop HandlerThreads
}
}
I would suggest two things.
Make Model, View, and Presenter into interfaces.
Your MVP-View (an Activity, Fragment, or View) should be so simple it does not need to be tested.
Your MVP-Presenter never directly interacts with the Activity/Fragment/View so it can be tested with JUnit. If you have dependencies on the Android Framework is bad for testing because you need to Mock out Android objects, use emulator, or use a Testing Framework like Roboelectric that can be really slow.
As an example of the interfaces:
interface MVPView {
void setText(String str);
}
interface MVPPresenter {
void onButtonClicked();
void onBind(MVPView view);
void onUnbind();
}
The MVPPresenter class now does not depend on the Android Framework:
class MyPresenter implements MVPPresenter{
MVPView view;
#Override void bind(MVPView view){ this.view = view; }
#Override void unbind() {this.view = null; }
#Override void onButtonClicked(){
view.setText("Button is Clicked!");
}
}
Instead of making the Presenter a static class, I would make it a Retained Fragment. Static objects need to be tracked carefully and removed for GC manually whenever they are not needed (otherwise it's considered a memory leak). By using a retain fragment, it is much easier to control the lifetime of the presenter. When the fragment that owns the retain fragment finishes, the retain fragment is also destroyed and the memory can be GC'd. See here for an example.
Activity, Fragments should have only overidden methods of View interface and other Android Activity, Fragment's methods.
View has methods like navigateToHome, setError, showProgress etc
Presenter interacts with both View and Interactor(has methods like onResume, onItemClicked etc)
Interactor has all the logics and calculations, does time intensive tasks such as db, network etc.
Interactor is android free, can be tested with jUnit.
Activity/fragment implements view, instantiate presenter.
Suggest edits to my understanding. :)
An example is always better than words, right?
https://github.com/antoniolg
You're on the right track, and you are correct to ask about static - whenever you notice that you have written that keyword, it's time to pause and reflect.
The Presenter's life should be tied directly to the Activity's/Fragment's. So if the Activity is cleaned up by GC, so should the presenter. This means that you should not hold a reference to the ApplicationContext in the presenter. It's ok to use the ApplicationContext in the Presenter, but it's important to sever this reference when the Activity is destroyed.
The Presenter should also take the View as a constructor parameter:
public class MainActivity extends Activity implements GameView{
public void onCreate(){
presenter = new GamePresenter(this);
}
}
and the presenter looks like:
public class GamePresenter {
private final GameView view;
public GamePresenter(GameView view){
this.view = view;
}
}
then you can notify the Presenter of the Activity LifeCycle Events like so:
public void onCreate(){
presenter.start();
}
public void onDestroy(){
presenter.stop();
}
or in onResume/onPause - try to keep it symmetrical.
In the end you only have 3 files:
(I'm taking some code from another explanation I gave here but the idea is the same.)
GamePresenter:
public class GamePresenter {
private final GameView view;
public GamePresenter(GameView view){
this.view = view;
NetworkController.addObserver(this);//listen for events coming from the other player for example.
}
public void start(){
applicationContext = GameApplication.getInstance();
}
public void stop(){
applicationContext = null;
}
public void onSwipeRight(){
// blah blah do some logic etc etc
view.moveRight(100);
NetworkController.userMovedRight();
}
public void onNetworkEvent(UserLeftGameEvent event){
// blah blah do some logic etc etc
view.stopGame()
}
}
I'm not sure exactly why you want the ApplicationContext instead of the Activity context, but if there's no special reason for that, then you can alter the void start() method to void start(Context context) and just use the Activity's context instead. To me this would make more sense and also rule out the need to create a singleton in your Application class.
GameView
is an interface
public interface GameView {
void stopGame();
void moveRight(int pixels);
}
GameFragment is a class that extends Fragment and implements GameView AND has a GamePresenter as a member.
public class GameFragment extends Fragment implements GameView {
private GamePresenter presenter;
#Override
public void onCreate(Bundle savedInstanceState){
presenter = new GamePresenter(this);
}
}
The key to this approach is to clearly understand the role of each file.
The Fragment is in control of anything view related (Buttons, TextView etc). It informs the presenter of user interactions.
The Presenter is the engine, it takes the information from the View (in this case it is the Fragment, but notice that this pattern lends itself well to Dependency injection? That's no coincidence. The Presenter doesn't know that the View is a Fragment - it doesn't care) and combines it with the information it is receiving from 'below' (comms, database etc) and then commands the View accordingly.
The View is simply an interface through which the Presenter communicates with the View. Notice that the methods read as commands, not as questions (eg getViewState()) and not to inform (eg onPlayerPositionUpdated()) - commands (eg movePlayerHere(int position)).
Related
This question is centered around the architecture of an Android Application. When using the LifeCycle component ViewModel, is it best to have one ViewModel per fragment or one ViewModel for the parent activity, to which the Fragments are subscribed to?
It seems unclear to me how to orient something like a Master-Detail fragment-activity relationship without some coupling. For instance, if each Fragment had it's own ViewModel, it is unclear how the Activity should know how to react without coupling (interface, direct functions calls).
As I mentioned in the comments, there is no unique way to accomplish this, but ideally, and very specifically to your Master/Detail flow concern, let's analyze the default provided example:
ItemDetialActivity handles fragment creation and display, FAB and menu actions. Note that there is nothing related to user data, only "system" handles . I, for instance, try to limit activities responsabilities to navigation, and stuff you really can't avoid like menu button handling. Now, ItemListActivity appears to be violating this principle because takes care of displaying the list (Google examples only create confusion -IMHO- between these separation of concerns), I would create a separate fragment that contains the RecyclerView and its adapter.
Now to the nitty gritty. Here is a very high-level skeleton I hope you can make use of. Check it out, implement it, and come back if there are any questions:
public interface BaseView {
LifecycleOwner lifecycleOwner();
/* perform actions that affect a basic screen status, like hide/show progress bars and errors,
animate views, etc. */
}
public class BaseRepo {
// will contain LiveData instances which will postValues()
}
public class FooRepo extends BaseRepo {
/* will contain access to database and networking functions, either by creating instance methods
or enforcing with an interface, it's up to you. */
}
public class BaseModel<P extends BasePresenter> extends ViewModel {
protected final FooRepo fooRepo; // optional, can be on concretes
<T> void subscribe(LiveData<T> liveData, Observer<T> observer) {
liveData.observe(view.lifecycleOwner(), observer);
}
<T> void unsubscribe(LiveData<T> liveData, Observer<T> observer) {
if (liveData != null) {
liveData.removeObserver(observer);
}
}
...
}
public abstract class BasePresenter<M extends BaseModel, V extends BaseView> implements LifecycleObserver {
protected V view;
protected M model;
public void setModel(M model) {
this.model = model;
}
public final void attachView(V view, Lifecycle lifecycle) {
this.view = view;
lifecycle.addObserver(this);
}
public void setPresenter(P presenter) {
this.presenter = presenter;
this.presenter.setModel(this);
}
...
}
public abstract class BaseFragment implements BaseView {
/* generics is highly encouraged here, I've seen examples of both BasePresenter<P>
and BaseView<P> */
protected P presenter;
/* You should bind layers here, or in the concrete class,
either with Dagger, reflection, or some other way */
#Override
public LifecycleOwner lifecycleOwner() {
return this;
}
...
}
Now, for every concrete screen you should create a presenter, model, and fragment that derive from the bases, and perform specifics there. I hope it helps.
I find myself where i need to play a sound file when user clicks a button on a view.
MediaPlayer requires a context to be created.
What is the best way to put MediaPlayer initialization code?
Should I pass a context into a presenter method and play it there?
Or is it ok to just play on the view.
Context is a part of Android View Layer in MVP, so Presenter must not have any idea about it and you should not pass it to presenter.
You have to add a methods to your View interface and implement it inside your android view components (i.e. Activity or Fragment) and use them to do an action in View layer as playing a sound.
Presenter must ask for the UI event and View must handle it!
Here is a MVP sample using Dagger, RxJava and Retrofit, which might help you to learn more about MVP in Android:
https://github.com/mmirhoseini/marvel
I often put business logic code in Model Layer (don't make confusion with model in database). I often rename as XManager for avoiding confusion (such as ProductManager, MediaManager ...) so presenter class just uses for keeping workflow.
The rule of thumb is no or at least limit import android package in presenter class. This best practice supports you easier in testing presenter class because presenter now is just a plain java class, so we don't need android framework for testing those things.
For example here is my mvp workflow.
View class: This is a place you store all your view such as button, textview ... and you set all listeners for those view components on this layer. Also on this View, you define a Listener class for presenter implements later. Your view components will call methods on this listener class.
class ViewImpl implements View {
Button playButton;
ViewListener listener;
public ViewImpl(ViewListener listener) {
// find all view
this.listener = listener;
playButton.setOnClickListener(new View.OnClickListener() {
listener.playSong();
});
}
public interface ViewListener {
playSong();
}
}
Presenter class: This is where you store view and model inside for calling later. Also presenter class will implement ViewListener interface has defined above. Main point of presenter is control logic workflow.
class PresenterImpl extends Presenter implements ViewListener {
private View view;
private MediaManager mediaManager;
public PresenterImpl(View, MediaManager manager) {
this.view = view;
this.manager = manager;
}
#Override
public void playSong() {
mediaManager.playMedia();
}
}
Manager class: Here is the core business logic code. Maybe one presenter will have many managers (depend on how complicate the view is). Often we get Context class through some injection framework such as Dagger.
Class MediaManagerImpl extends MediaManager {
// using Dagger for injection context if you want
#Inject
private Context context;
private MediaPlayer mediaPlayer;
// dagger solution
public MediaPlayerManagerImpl() {
this.mediaPlayer = new MediaPlayer(context);
}
// no dagger solution
public MediaPlayerManagerImpl(Context context) {
this.context = context;
this.mediaPlayer = new MediaPlayer(context);
}
public void playMedia() {
mediaPlayer.play();
}
public void stopMedia() {
mediaPlayer.stop();
}
}
Finally: Put those thing together in Activities, Fragments ... Here is the place you initialize view, manager and assign all to presenter.
public class MyActivity extends Activity {
Presenter presenter;
#Override
public void onCreate() {
super.onCreate();
IView view = new ViewImpl();
MediaManager manager = new MediaManagerImpl(this.getApplicationContext());
// or this. if you use Dagger
MediaManager manager = new MediaManagerImpl();
presenter = new PresenterImpl(view, manager);
}
#Override
public void onStop() {
super.onStop();
presenter.onStop();
}
}
You see that each presenter, model, view is wrapped by one interface. Those components will called through interface. This design will make your code more robust and easier for modifying later.
This is such a long post for answering your question. I think this is suitable because everyone has their own MVP implementation (core flow is same, but minorities are different). So I present here a workflow I often use in work. Hoping you see this useful :)
If you need a generic Context you can extend Application, declare a static context variable and after you can get this Context into your presenter.
I'm new to Android and MVP in-general, and I've been doing iOS programming for the last 1.5 years, so I find delegate patterns easy to digest. I've implemented MVP in such a way that the view conforms to a presenter's protocol, which lets the presenter disregard the view's specific type, but lets it know that certain methods are a given and thus okay to call on the "view." I've been reading various MVP guides, and all of the Mosby tutorials, and I'm not sure I agree with some of it. Is the pattern I've implemented kosher? I'd like some feedback so I don't keep heading in a bad direction, if that is indeed what I'm doing...
For example,
Base Presenter:
public abstract class Presenter<V, S> implements BasePresenterInterface<V, S> {
public interface PresenterProtocol extends BasePresenterProtocol {
}
private WeakReference<V> mAttachedView = null;
private S mService = null;
/**
* Interface Overrides
*/
#Override
public void attachView(V view) {
boolean viewDoesNotConform = !viewDoesConform(view);
if (viewDoesNotConform) {
Log.d("DEBUG", "Cannot attach View that does not conform to PresenterProtocol");
return;
}
mAttachedView = new WeakReference<>(view);
((BasePresenterProtocol) getAttachedView()).onViewAttached();
}
#Override
public void detachView() {
mAttachedView = null;
}
#Override
public boolean viewDoesConform(V view) {
Class<?> klass = view.getClass();
boolean conforms = BasePresenterInterface.BasePresenterProtocol.class.isAssignableFrom(klass);
return conforms;
}
#Override
public boolean viewIsAttached() {
return mAttachedView != null;
}
#Override
public V getAttachedView() {
return mAttachedView.get();
}
#Override
public S getService() {
return mService;
}
#Override
public void setService(S service) {
mService = service;
}
}
I then subclass this into the following:
PhotoRecyclerPresenter:
public class PhotoRecyclerPresenter extends Presenter<PhotoRecyclerPresenter.PhotoRecyclerPresenterProtocol, PhotoService> {
public interface PhotoRecyclerPresenterProtocol extends Presenter.PresenterProtocol {
void onPhotosLoaded(List<TestPhoto> photoList);
void onItemSelected(TestPhoto photo);
void onShowDetail(TestPhoto photo);
}
private static PhotoRecyclerPresenter mSharedInstance;
private PhotoRecyclerPresenter() {
setService(new PhotoService());
}
/**
* External Methods
*/
public void getPhotos() {
boolean noAttachedView = !viewIsAttached();
if (noAttachedView) {
Log.d("DEBUG", "No view attached");
return;
}
getService().getAPI()
.getPhotos()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(photoList -> getAttachedView().onPhotosLoaded(photoList));
}
/**
* Internal Methods
*/
public static PhotoRecyclerPresenter getSharedInstance() {
boolean firstInstance = mSharedInstance == null;
if (firstInstance) {
setSharedInstance(new PhotoRecyclerPresenter());
}
return mSharedInstance;
}
public static void setSharedInstance(PhotoRecyclerPresenter instance) {
mSharedInstance = instance;
}
public void didSelectItem(TestPhoto photo) {
getAttachedView().showDetail(photo);
}
}
And it communicates with the view:
PhotoRecyclerFragment:
public class PhotoRecyclerFragment extends Fragment implements PhotoRecyclerPresenter.PhotoRecyclerPresenterProtocol {
private RecyclerView mRecyclerView;
private RecyclerView.LayoutManager mLayoutManager;
private Activity mParentActivity;
private PhotoRecyclerPresenter mPresenter;
private PhotoRecyclerAdapter mAdapter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_recycler, container, false);
mParentActivity = getActivity();
mRecyclerView = (RecyclerView) rootView.findViewById(R.id.recyclerView);
mLayoutManager = new LinearLayoutManager(mParentActivity);
mAdapter = new PhotoRecyclerAdapter(this);
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setAdapter(mAdapter);
mPresenter = PhotoRecyclerPresenter.getSharedInstance();
mPresenter.attachView(this);
return rootView;
}
#Override
public void onDestroyView() {
super.onDestroyView();
mPresenter.detachView();
mAdapter.clear();
}
/**
* PhotoRecyclerPresenterProtocol Methods
*/
#Override
public void onItemSelected(TestPhoto photo) {
mPresenter.didSelectItem(photo);
}
#Override
public void onPhotosLoaded(List<TestPhoto> photoList) {
mAdapter.loadPhotos(photoList);
}
#Override
public void onViewAttached() {
mPresenter.getPhotos();
}
#Override
public void onViewDetached() {
}
#Override
public void onShowDetail(TestPhoto photo) {
Intent detailIntent = new Intent(mParentActivity, PhotoDetailActivity.class);
mParentActivity.startActivity(detailIntent.putExtra(Intent.EXTRA_UID, photo.getPhotoId()));
}
}
This lets me define a set of requirements a view needs to conform to in order to utilize the singleton presenter, while keeping the presenter agnostic about what views use it, as long as they conform to its protocol. So far in my practice project it seems to work fine, but I can't seem to find any resources where what I'm doing is recommended as far as MVP goes, and I have enough self-doubt that I figured I'd ask my first StackOverflow question. Can anyone who has experience with MVP shed some light on this?
Also, if I'm asking in the wrong place, feel free to point me to the correct place to post this.
Thanks :)
From my point of view you are doing the same thing that Mosby does. The only difference is the name of the interface (or protocol in objective-c) world. You call it PresenterProtocol while Mosby call it MvpView. Both are doing the same job: Offering the Presenter an Api of methods the presenter can call to manipulate the view.
The only thing that doesn't make sense is to have a method viewDoesConform(). In Java you have type safety. You can use the generics type V of your Presenter to ensure that your fragment is implementing the Presenter's protocol. just change it to V extends BasePresentersProtocol
Furthermore I think that it doesn't make sense to have a "shared instance" (a.k.a Singleton pattern) of the presenter. I think it would make more sense to have a "shared instance" of the PhotoService. But But please note also that by doing so your code is not testable (unit tests) anymore. You should google for Dependency injection or Inverse of Control to understand how to write modular, reusable and testable code. I'm not talking about dependency injection frameworks like Dagger , spring or guice. You just should understand the idea behind dependency injection. You can write classes following this principle completely without dependency injection frameworks (i.e. using constructor parameters).
Side note: you never unsubscribe your presenter from PhotoService. Depending on how PhotoService is implemented you may have a memory leak because PhotoService observable has a reference to the presenter which prevents the presenter and PhotoService (depending on your concrete implementation) from being garbage collected.
Edit: Mosby defines the protocol for the View. Have a look at the getting started section on the project website. The HelloWorldView defines two methods: showHello() and showGoodbye() (implented by the HelloWorldActivity) and HelloWorldPresenter calls these two methods to manipulate the View. The HelloWorldPresenter also cancels the async requests to avoid memory leaks. You should do that too. Otherwise your presenter can only be garbage collected after the retrofit httpcall has completed.
I am attempting to implement the MVP pattern in a new Android application. I have two activities, and have defined two View interfaces, one for each of the activities. For example:
public class MyWindow1 implements IView1
public class MyWindow2 implements IView2
And I have a common Presenter class:
public class MainPresenter implements Serializable {
public MainPresenter() {
}
public void setView1(IView1 view1) {
this.view1 = view1;
}
public void setView2(IView2 view2) {
this.view2 = view2;
}
This is all fairly straightforward so far. Now, the activities register with the presenter when they are first created, for example for view1:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_menu);
mainPresenter = new MainPresenter();
mainPresenter.setView1(this);
}
However, I have a problem now - I need to be able to create activity 2 (i.e. view2) and register it with the Presenter. But I cannot do this as the only (it seems?) way to pass objects between activities is the incredibly impractical method of using serialization. Which doesn't work in this case because I have the references back to the views, which would get lost during serialization.
How can I llink a single presenter to two views, ideally without just declaring the presenter as a static global, which I am very close to doing.
I'm using greenrobot's EventBus in my android apps and I absolutely like it.
However, now I'd like to seperate the logic from my fragments by using presenters (MVP).
Is the following possible and is it useful?
Fragment:
public class MyFragment implements IMyFragment {
IMyPresenter mPresenter;
#Override
public View onCreateView(...) {
mPresenter = new MyPresenter(this);
}
#Override
public void onResume() {
// EventBus.getDefault().register(mPresenter); // register presenter to bus
mPresenter.resume();
}
#Override
public void onPause() {
// EventBus.getDefault().unregister(mPresenter); // unregister presenter from bus
mPresenter.pause();
}
#Override
public void doSomething() { // gets called via presenter
// ...
}
}
Presenter:
public class MyPresenter implements IMyPresenter {
IMyFragment mFragment;
// constructor to inject fragment
public MyPresenter(IMyFragment mFragment) {
this.mFragment = mFragment;
}
// handle event
public void onEvent(SomeEvent event) {
mFragment.doSomething();
}
public void resume() {
EventBus.getDefault.register(this);
}
public void pause() {
EventBus.getDefault.unregister(this);
}
}
Does this make sense?
Or is it even dangerous regarding unregistering the presenter from the bus and the complex fragment lifecycle?
Edit: Moved bus registration to presenter itself (Thanks to Nicklas).
Any more comments on this architecture?
You're putting too much responsibility on the View. What you want to do instead is have your Presenter expose a resume() and pause() method, and call those in your View. In those methods you'll register() and unregister() on the EventBus.
This puts all the event-handling code in your Presenter. It also means that you can change the event mechanism you use in your presenter, at any time, without having to change a line of code in your View.
In MVP, the only object you'll want to call non-view-related methods on, from the View, is the associated Presenters.