Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
167 views
in Technique[技术] by (71.8m points)

android - Separating the Concerns of Activity and GoogleApiClient

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 in concerns.

After rewriting this LoginActivity code several times, in many different apps, the easy (and not so elegant) solution was create the Google API client as a Application class object. But, since the connection state affect the UX flow, I never was happy about with this approach.

Is there an elegant way of place the GoogleApiClient outside the Activity?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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 ;-)
    }
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...