6
votes

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
    }

}
3
I've been thinking about this for a while and have started writing a blog on the topic if you're still interested: cj65535.blogspot.com.au/2017/03/…C B J

3 Answers

6
votes

I would suggest two things.

  1. 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!");
    }
}
  1. 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.
4
votes
  1. Activity, Fragments should have only overidden methods of View interface and other Android Activity, Fragment's methods.
  2. View has methods like navigateToHome, setError, showProgress etc
  3. Presenter interacts with both View and Interactor(has methods like onResume, onItemClicked etc)
  4. Interactor has all the logics and calculations, does time intensive tasks such as db, network etc.
  5. Interactor is android free, can be tested with jUnit.
  6. Activity/fragment implements view, instantiate presenter.

Suggest edits to my understanding. :)

An example is always better than words, right? https://github.com/antoniolg

2
votes

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)).