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:

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:

    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:

    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