Separating the Concerns of Activity and GoogleApiClient

前端 未结 1 1498
悲&欢浪女
悲&欢浪女 2020-12-06 03:11

As usual there is a lot of code in my LoginActivity and I really would prefer to separate the Activity responsibilities from the Google Play sign i

相关标签:
1条回答
  • 2020-12-06 03:33

    0. TL;DR

    For the impatient coder, a working version of the following implementation can be found on GitHub.

    Reducing our problem only to the connection concept, we may consider that:

    1. It has finite states.
    2. It encapsulates the connection client.
    3. It is (rather) be unique.
    4. The current state affect the behavior of the app.

    1. State Pattern

    This is a behavioral pattern the allow an object to alter its behavior when its internal state changes. The GoF Design Patterns book describes how a TCP connection can be represent by this pattern (which is also our case).

    A state from a state machine should be a singleton, and the easiest away of doing it in Java was to create Enum named State as follows:

    public enum State {
        CREATED {
            void connect(Connection connection) {
                connection.onSignUp();
            }
        },
        OPENING {
            void connect(Connection connection) {
                connection.onSignIn();
            }
        },
        OPENED {
            void disconnect(Connection connection) {
                connection.onSignOut();
            }
            void revoke(Connection connection) {
                connection.onRevokeAndSignOut();
            }
        },
        CLOSED {
            void connect(Connection connection) {
                connection.onSignIn();
            }
        };
    
        void connect(Connection connection) {}
        void disconnect(Connection connection) {}
        void revoke(Connection connection) {}
    }
    

    The Activity will communicate with the Connection abstract class (which holds the context) through the methods connect(), disconnect(), and revoke(). The current state defines how these methods will behave:

    public void connect() {
        currentState.connect(this);
    }
    
    public void disconnect() {
        currentState.disconnect(this);
    }
    
    public void revoke() {
        currentState.revoke(this);
    }
    
    private void changeState(State state) {
        currentState = state;
        setChanged();
        notifyObservers(state);
    }
    

    2. Proxy Pattern

    The class GoogleConnection inherits from Connection and encapsulates the GoogleApiClient, so it must provide both ConnectionCallbacks and OnConnectionFailedListener as follows:

    @Override
    public void onConnected(Bundle connectionHint) {
        changeState(State.OPENED);
    }
    
    @Override
    public void onConnectionSuspended(int cause) {
        mGoogleApiClient.connect();
    }
    
    @Override
    public void onConnectionFailed(ConnectionResult result) {
        if (state.equals(State.CLOSED) && result.hasResolution()) {
            changeState(State.CREATED);
            connectionResult = result;
        } else {
            connect();
        }
    }
    
    public void onActivityResult(int resultCode) {
        if (resultCode == Activity.RESULT_OK) {
            connect();
        } else {
            changeState(State.CREATED);
        }
    }
    

    The methods onSignIn(), onSignUp(), onSignOut(), and onRevokeAndSignOut are required on the second step of this explanation.

    public void onSignUp() {
        try {
            Activity activity = activityWeakReference.get();
            changeState(State.OPENING);
            connectionResult.startResolutionForResult(activity, REQUEST_CODE);
        } catch (IntentSender.SendIntentException e) {
            changeState(State.CREATED);
            mGoogleApiClient.connect();
        }
    }
    
    public void onSignIn() {
        if (!mGoogleApiClient.isConnected() && !mGoogleApiClient.isConnecting()) {
            mGoogleApiClient.connect();
        }
    }
    
    public void onSignOut() {
        Plus.AccountApi.clearDefaultAccount(mGoogleApiClient);
        mGoogleApiClient.disconnect();
        changeState(State.CLOSED);
        mGoogleApiClient.connect();
    }
    
    public void onRevokeAndSignOut() {
        Plus.AccountApi.clearDefaultAccount(mGoogleApiClient);
        Plus.AccountApi.revokeAccessAndDisconnect(mGoogleApiClient);
        changeState(State.CLOSED);
        mGoogleApiClient = mGoogleApiClientBuilder.build();
        mGoogleApiClient.connect();
    }
    

    3. Singleton Pattern

    Since there is not need to recreate this class repeatedly, we provide it as a singleton:

    public static Connection getInstance(Activity activity) {
        if (null == sConnection) {
            sConnection = new GoogleConnection(activity);
        }
    
        return sConnection;
    }
    
    public void onActivityResult(int result) {
        if (result == Activity.RESULT_OK) {
            changeState(State.CREATED);
        } else {
            changeState(State.CLOSED);
        }
        onSignIn();
    }
    
    private GoogleConnection(Activity activity) {
        activityWeakReference = new WeakReference<>(activity);
    
        googleApiClientBuilder = new GoogleApiClient
               .Builder(activity)
               .addConnectionCallbacks(this)
               .addOnConnectionFailedListener(this)
               .addApi(Plus.API, Plus.PlusOptions.builder().build())
               .addScope(new Scope("email"));
    
        googleApiClient = googleApiClientBuilder.build();
        currentState = State.CLOSED;
    
        googleApiClient.connect();
    }
    

    4. Observable Pattern

    The Connection class extends Java Observable, so one or many activities can observe the state changes:

    @Override
    protected void onCreate(Bundle bundle) {
        mConnection = GoogleConnection.getInstance(this);
        mConnection.addObserver(this);
    }
    
    @Override
    protected void onDestroy() {
        mConnection.deleteObserver(this);
    }
    
    @Override
    protected void onActivityResult(int request, int result, Intent data) {
        if (Connection.REQUEST_CODE == request) {
            mConnection.onActivityResult(result);
        }
    }
    
    @Override
    public void update(Observable observable, Object data) {
        if (observable == mGoogleConnection) {
            // UI/UX magic happens here ;-)
        }
    }
    
    0 讨论(0)
提交回复
热议问题