2

I am refactoring one of my old apps in MVP. I have refactored most of the logic and now I am stuck with following one. In one of my Activities, I have implemented PhoneStateLister as following.

private class CallStateListener extends PhoneStateListener {

    public void onCallStateChanged(int state, String incomingNumber) {

        switch (state) {
            case TelephonyManager.CALL_STATE_IDLE:
                deactivateSpeaker();
                if (callCount == 0) {
                    doCall();
                } else {
                    checkForHangUp();
                }
                break;
            case TelephonyManager.CALL_STATE_OFFHOOK:
                checkAutoSpeaker();
                break;
            case TelephonyManager.CALL_STATE_RINGING:
                break;
            default:
                break;
        }
    }

    private void checkAutoSpeaker() {
        if (preferenceManager.isAutoSpeaker()) activateSpeaker();
    }

    private void activateSpeaker() {
        final Handler mHandler = new Handler();
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                audioManager.setMode(AudioManager.MODE_IN_CALL);
                audioManager.setSpeakerphoneOn(true);
            }
        }, 1000);
    }

    private void deactivateSpeaker() {
        audioManager.setMode(AudioManager.MODE_NORMAL); //Deactivate loudspeaker
    }

}

I am having a hard time deciding where to put this logic in MVP. Should Activity handle this PhoneStateListener or should presenter handle this? Please help me with the solution. Thanks.

Dhaval
  • 2,724
  • 2
  • 24
  • 33
  • check this [tutorial](http://www.truiton.com/2014/08/android-phonestatelistener-example/) may help. – Mohammed Farhan Mar 08 '18 at 05:03
  • Thanks, @MohammedFarhan. But that tutorial only describes how to implement PhoneStateListener, which I already know. As I stated, I want to know that, should Activity or Fragment be responsible for handling that Listener? – Dhaval Mar 08 '18 at 05:08
  • You might have not seen that tutorial properly. `CustomPhoneStateListener` is a custom class with all listeners in it and in activity this class is being implemented to listen telephonymanager. – Mohammed Farhan Mar 08 '18 at 05:12

2 Answers2

0

Setting up listeners in an MVP pattern is always a little tricky. I am pretty sure that there is no “right” or “wrong” answer to your question, since it heavily depends on your personal preferences and architecture style. But as I general rule it is always smart to move as much business logic to the presenter and keep everything else (this also includes listeners) as dumb as possible.

That is why I would suggest that you add an interface to your listener class and keep a (weak) reference to the presenter there. The only thing that the listener does in that example is listening to the state change and then forwarding the information to the presenter (where it gets distributed).

public class CallStateListener extends PhoneStateListener {
    private final WeakReference<CallStateListenerInterface> presenterInterface;
    public CallStateListener(CallStateListenerInterface presenterInterface){
        this.presenterInterface = new WeakReference<>(presenterInterface);
    }
    public void onCallStateChanged(int state, String incomingNumber){
        presenterInterface.get().onCallStateListenerCallStateChanged(state, incomingNumber);
    }
    public interface CallStateListenerInterface {
        void onCallStateListenerCallStateChanged(int state, String incomingNumber);
    }
}

You presenter needs to implement the CallStateListenerInterface (of course) and handles all the business logic when the call state changes.

public class myPresenter implements MVPInterface, CallStateListenerInterface {

    /*your business logic*/

    public void onCallStateListenerCallStateChanged(int state, String incomingNumber){
        switch (state) { 
           case TelephonyManager.CALL_STATE_IDLE: 
                deactivateSpeaker(); 
                if (callCount == 0) { 
                    doCall(); 
                } else { 
                    checkForHangUp(); 
                } 
               break; 
           case TelephonyManager.CALL_STATE_OFFHOOK: 
               checkAutoSpeaker(); 
               break; 
           case TelephonyManager.CALL_STATE_RINGING: 
               break; 
           default: 
               break; 
        }
    }
}

From here everything that needs to interact with the view will be called, but keep those calls to simple one line instructions and leave everything complicated for the presenter to decide.

If you don't want to add a new interface for each listener class you can also just pass the whole MVPInterface to the CallStateChangedListener (and declare everything at a central place in your contract).

If you implement the listener this way you got a pretty clean architecture that should be easily testable. I hope this was helpfull and please let me know if you have any questions :)

SMALL ALTERNATIVE IMPLEMENTATION:

As pointed out by a commenter: In my original suggestion, the presenter accesses the static phone state integers from the TelephonyManager class. This is not the cleanest design, since the presenter should be kept completely separate from any Android specific code. Since there are no real super clean ways to work around this, I can only suggest a mapping from the TelephonyManager states to a (self defined) phone state that is kept in the presenter (or better yet: a static constants file). Basicly that means the onCallStateChanged in the CallStateListener should look something like this:

public void onCallStateChanged(int state, String incomingNumber){
    int stateValue = myPresenter.PRESENTER_CALL_STATE_IDLE;
    if (state == TelephonyManager.CALL_STATE_RINGING){
        stateValue = myPresenter.PRESENTER_CALL_STATE_RINGING;
    } else if (state == TelephonyManager.CALL_STATE_OFFHOOK){
        stateValue = myPresenter.PRESENTER_CALL_STATE_OFFHOOK;
    }
    presenterInterface.get().onCallStateListenerCallStateChanged(stateValue , incomingNumber);
}

With this implementation you could move the TelephonyManager states out of the presenter and use your own defined ones. (once again: I am really not sure if this is worth the effort, but I wanted to at least offer the alternative)

Bmuig
  • 1,059
  • 7
  • 12
  • 1
    I can only disagree with this, as presenters should not contain any platform specific logic (i.e. should import no android classes) and should contain only pure java – Tim Apr 03 '18 at 12:21
  • yes, I completely agree ... but the only android specific components that my suggestion uses are the static TelephoneManager Integers inside the state switch. I admit that this is not super clean, but every alternative solution makes the code much more dirty ... – Bmuig Apr 03 '18 at 12:26
0

I believe you should leave the listener in your fragment/activity section. I have in mind two solutions. The first one is to leave the switch/case in your listener and move a little bit of logic into your presenter:

public void onCallStateChanged(int state, String incomingNumber) {
    switch (state) {
        case TelephonyManager.CALL_STATE_IDLE:
            mPresenter.onCallStateIDLE();
            break;
        case TelephonyManager.CALL_STATE_OFFHOOK:
            mPresenter.onCallStateOffHook();
            break;
        case TelephonyManager.CALL_STATE_RINGING:
            mPresenter.onCallStateRinging();
            break;
        default:
            break;
    }
}

and in your presenter:

void onCallStateIDLE(){
    mView.deactivateSpeaker();
    if (callCount == 0) {
        mView.doCall();
    } else{
        mView.checkForHangUp();
    }
}

The other option should be to move the switch/case into the presenter. The tricky part should be what to do with the TelephonyManager constants. As you know, you should leave the presenter as a pure java code.

You should have a View interface, there you can do something like this

interface MyActivityContract{
    interface View{
        int CALL_STATE_IDLE = TelephonyManager.CALL_STATE_IDLE;
        //etc etc the same with the rest of constants
    }
    interface Presenter{}
}

then in your listener, you should have a reference to your presenter

private class CallStateListener extends PhoneStateListener {
    public void onCallStateChanged(int state, String incomingNumber) {
        mPresenter.onCallStateChanged(state, incomingNumber);
    }
}

finally your presenter could be something like this

public void onCallStateChanged(int state, int incomingNumber){
    switch (state) {
        case MyActivityContract.View.CALL_STATE_IDLE:
            mView.deactivateSpeaker();
            if (callCount == 0) {
                mView.doCall();
            } else {
                mView.checkForHangUp();
            }
            break;
        case MyActivityContract.View.CALL_STATE_OFFHOOK:
            if (preferenceManager.isAutoSpeaker()){
                mView.activateSpeaker();
            }
            break;
        case MyActivityContract.View.CALL_STATE_RINGING:
            break;
        default:
            break;
    }
}

in this way you don't have any android reference in your presenter, but you have it in your contract.....

YorchSircam
  • 168
  • 8