android - services - Separando las preocupaciones de la actividad y GoogleApiClient
google play services android api (1)
Como de costumbre, hay un montón de código en mi LoginActivity
y realmente preferiría separar las responsabilidades de la Activity
de las preocupaciones del signo de Google Play.
Después de volver a escribir este código de LoginActivity
varias veces, en muchas aplicaciones diferentes, la solución fácil (y no tan elegante) fue crear el cliente de API de Google como un objeto de clase de Application
. Pero, dado que el estado de la conexión afecta el flujo UX, nunca estuve contento con este enfoque.
¿Existe una forma elegante de ubicar GoogleApiClient
fuera de la Activity
?
0. TL; DR
Para el codificador impaciente, se puede encontrar una versión funcional de la siguiente implementación en GitHub .
Reduciendo nuestro problema solo al concepto de conexión , podemos considerar que:
- Tiene estados finitos.
- Encapsula el cliente de conexión.
- Es (más bien) ser único.
- El estado actual afecta el comportamiento de la aplicación.
1. Patrón de estado
Este es un patrón de comportamiento que permite a un objeto alterar su comportamiento cuando cambia su estado interno. El libro GoF Design Patterns describe cómo se puede representar una conexión TCP mediante este patrón (que también es nuestro caso).
Un estado de una máquina de estados debería ser un singleton
, y lo más fácil de hacerlo en Java era crear Enum
named State
siguiente manera:
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) {}
}
La Activity
se comunicará con la clase abstracta de Connection
(que contiene el contexto) a través de los métodos connect()
, disconnect()
y revoke()
. El estado actual define cómo se comportarán estos métodos:
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. Patrón de Proxy
La clase GoogleConnection
hereda de Connection
y encapsula GoogleApiClient
, por lo que debe proporcionar ConnectionCallbacks
y OnConnectionFailedListener
siguiente manera:
@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);
}
}
Los métodos onSignIn()
, onSignUp()
, onSignOut()
y onRevokeAndSignOut
son necesarios en el segundo paso de esta explicación.
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. Patrón Singleton
Como no es necesario recrear esta clase repetidamente, lo proporcionamos como 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. Patrón observable
La clase Connection
amplía Java Observable
, por lo que una o más actividades pueden observar los cambios de estado:
@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 ;-)
}
}