Search code examples
design-patternsmvpandroid-mvp

How to avoid ping pong method call in MVP?


In my Android app, I have a Fragment in MVP pattern. Lets assume we have:

  • CalculationFragment (the view)
  • CalculationPresenter (the presenter)
  • CalculationNetwork (the model)

I need a multi-step calculation or REST call:

  1. In CalculationFragment's onViewCreated() I call val sessionId = presenter.firstCall() as String to retrieve a sessionToken for further process.
  2. Then the presenter retrieves the sessionToken via REST. So far so cool. Then I need the next call in chain, what consumes the retrieved sessionToken and a NfcAdapter.getDefaultAdapter(activity).

val jsonObject = secondCall(sessionId, nfcAdapter) .

Since I am in the presenter, I do neither have the activity nor the NfcAdapter here (and I honestly do not want to). I have two options here:

  1. Detour over view In my presenter I go back to my Fragment with the sessionToken view?.onFirstCallResult(sessionToken) and call from the CalculationFragment's onFirstCallResult() immediately presenter.secondCall(sessionToken, NfcAdapter.getDefaultAdapter(activity)).
  2. Short way, handled from pesenter I hand over the activity/NfcAdapter for the second call in the first call already and store it in the presenter. I would not need to pingpong between view and presenter a lot. Furtheron, I could remain in the presenter for all my calls?

What would be an elegant solution / pattern here?


Solution

  • Add more logic to the Presenter, Model or a Command thus removing it from the View. This is usually a better approach. Take into account the Single Responsibility principle and move application/domain logic from the View.

    Here are couple of ways you can do this:

    • Use a Command. The Presenter will create and invoke it. When the Command finishes the Presenter will return results to the View.

    • Design your Presenter to do the work like you did in you second diagram. If the presenter is simple that's OK. For more complex scenarios use Commands to separate the responsibility of performing the logic from the responsibility of when it should be invoked.

    In your case you need to get the NfcAdapter from the View to the Presenter and to a Command (if you have one).

    Here are couple of ways you can do it:

    • Pass it in the constructor of the Presenter
    • Add a special initialization method in the Presenter and pass all the dependencies it needs to this method (public void initialize(NfcAdapter adapter, ...))
    • Add a method to the View that the Presenter can call to get the adapter when it needs it (NfcAdapter view.getAdapter()).
    • Pass it as a parameter to the method call (like you did);

    Choosing an approach depends of several factors, developer taste being one of them Personally I would choose either method 1 or 2. I like to initialize the dependencies of an object (the Presenter in this case) at the begging of it's life-cycle, if this object will be needed them all the time and they don't change. Pass them in the method call if they change for every time this method is called. In this case I don't think you will change the NfcAdapter.

    Let's design a Command. Because you have a rather general description and the exact sequence is not described (first_call(), second_call() are too general), I'll design a simple non specific system that makes couple of calls. I'll use pseudo code. Most of the things are non specific as I don't know return types and stuff.

    Let's call this command CalculateCommand. This command will use CalculationModel for the calculation. Next let's define a TokenService that will contain the logic for getting the token (API call).

    public class TokenService {
       public SessionToken getToken() { ... }
    }
    
    public class CalculationResult {
      // represent whatever the result is...
    }
    
    public class CalculateCommand {
    
        private NfcAdapter mNfcAdapter;
        private TokenService mTokenService;
        private CalculationModel mCalculationModel;
    
        private SessionToken mSessionToken;
    
        public CalculateCommand(
          NfcAdapter nfcAdapter, 
          TokenService tokenService, 
          CalculationModel calculationModel) {
    
          mAdapter = adapter;
          mTokenService = tokenService;
          mCalculationModel = calculatioModel;
       }
    
       public CalculationResult Execute() {
    
         startSession();
    
         // do more stuff if you need to 
    
         val result = calculate();
    
         return result;
      }
    
      private void startSession() {
         mSessionToken = mTokenService.getToken();
      }
    
      private Result calcualte() {
        //not sure what parameters it needs but pass them here
        return mCalculatioModel.calculate(params...);
      }
    }
    
    public class Presenter {
    
        private View mView;
        private NfdAdapter mAdapter;
        private CalculationModel mModel;
        private TokenService mTokenService;
    
        public Presenter(View view, NfdAdapter adapter) {
    
          mView = view;
          mNfcAdapter = adapter;
          mModel = new CalculationModel();
    
         // or get if from a Service Locator, DI whatever.. if you need to mock the 
         // TokenService for unittests
    
         mTokenService = new TokenService(); 
       }
    
      public void performCalculation() {
    
        val cmd = CalculationCommand(mAdapter, mTokenService, mModel);
    
        val result = cmd.execute();
    
        mView.setResult(result);    
     }
    
     public class View {
    
        private Presenter mPresenter;
    
        public View() {
           mPresenter = new Presenter(this, NfcAdapter.getDefault(activity);
        }
    
        public void onViewCreated() {
           mPresenter.performCalculation();
        }
    
        public void setResult(Result result) {
           // do something with the result
        }
    }
    

    Check these resources for more information on MVP and it's flavors:

    https://martinfowler.com/eaaDev/uiArchs.html

    https://www.martinfowler.com/eaaDev/PassiveScreen.html

    https://martinfowler.com/eaaDev/SupervisingPresenter.html