android - plus - ir a google+
¿Cómo utilizar correctamente Google Plus Iniciar sesión con múltiples actividades? (3)
0. TL; DR
Para el codificador impaciente, se puede encontrar una versión de trabajo de la siguiente implementación en GitHub .
Después de volver a escribir el código de actividad de inicio de sesión varias veces en muchas aplicaciones diferentes, la solución fácil (y no tan elegante) fue crear el cliente API de Google como un objeto de clase de aplicación. Pero, dado que el estado de conexión afecta el flujo de UX, nunca estuve feliz con este enfoque.
Al reducir nuestro problema solo al concepto de conexión , podemos considerar que:
- Oculta el cliente API de Google.
- Tiene estados finitos.
- Es un (más bien) único.
- El estado actual afecta el comportamiento de la aplicación.
1. Patrón de proxy
Dado que la Connection
encapsula el GoogleApiClient
, implementará ConnectionCallbacks
y OnConnectionFailedListener
:
@Override
public void onConnected(Bundle hint) {
changeState(State.OPENED);
}
@Override
public void onConnectionSuspended(int cause) {
changeState(State.CLOSED);
connect();
}
@Override
public void onConnectionFailed(ConnectionResult result) {
if (currentState.equals(State.CLOSED) && result.hasResolution()) {
changeState(State.CREATED);
connectionResult = result;
} else {
connect();
}
}
Las actividades pueden comunicarse con la clase de Conexión a través de los métodos connect
, disconnect
y revoke
, pero sus comportamientos son decididos por el estado actual. Los siguientes métodos son requeridos por la máquina de estado:
protected void onSignIn() {
if (!googleApiClient.isConnected() && !googleApiClient.isConnecting()) {
googleApiClient.connect();
}
}
protected void onSignOut() {
if (googleApiClient.isConnected()) {
Plus.AccountApi.clearDefaultAccount(googleApiClient);
googleApiClient.disconnect();
googleApiClient.connect();
changeState(State.CLOSED);
}
}
protected void onSignUp() {
Activity activity = activityWeakReference.get();
try {
changeState(State.OPENING);
connectionResult.startResolutionForResult(activity, REQUEST_CODE);
} catch (IntentSender.SendIntentException e) {
changeState(State.CREATED);
googleApiClient.connect();
}
}
protected void onRevoke() {
Plus.AccountApi.clearDefaultAccount(googleApiClient);
Plus.AccountApi.revokeAccessAndDisconnect(googleApiClient);
googleApiClient = googleApiClientBuilder.build();
googleApiClient.connect();
changeState(State.CLOSED);
}
2. 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 de patrones de diseño de GoF describe cómo una conexión TCP puede ser representada por este patrón (que también es nuestro caso).
Un estado de una máquina de estado debe ser un singleton
, y la forma más fácil de hacerlo en Java fue crear un Enum
denominado State
siguiente manera:
public enum State {
CREATED {
@Override
void connect(Connection connection) {
connection.onSignUp();
}
@Override
void disconnect(Connection connection) {
connection.onSignOut();
}
},
OPENING {},
OPENED {
@Override
void disconnect(Connection connection) {
connection.onSignOut();
}
@Override
void revoke(Connection connection) {
connection.onRevoke();
}
},
CLOSED {
@Override
void connect(Connection connection) {
connection.onSignIn();
}
};
void connect(Connection connection) {}
void disconnect(Connection connection) {}
void revoke(Connection connection) {}
La clase de Connection
contiene el contexto, es decir, el estado actual, que define cómo se connect
, disconnect
y revoke
los métodos de connect
:
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);
}
3. Patrón Singleton
Dado que no es necesario recrear esta clase repetidamente, la proporcionamos como un singleton:
public static Connection getInstance(Activity activity) {
if (null == sConnection) {
sConnection = new Connection(activity);
}
return sConnection;
}
public void onActivityResult(int result) {
if (result == Activity.RESULT_OK) {
changeState(State.CREATED);
} else {
changeState(State.CLOSED);
}
onSignIn();
}
private Connection(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;
}
4. Patrón observable
La clase de Connection
extiende Java Observable
, por lo que 1 o más actividades pueden observar los cambios de estado:
@Override
protected void onCreate(Bundle bundle) {
connection = Connection.getInstance(this);
connection.addObserver(this);
}
@Override
protected void onStart() {
connection.connect();
}
@Override
protected void onDestroy() {
connection.deleteObserver(this);
connection.disconnect();
}
@Override
protected void onActivityResult(int request, int result, Intent data) {
if (Connection.REQUEST_CODE == request) {
connection.onActivityResult(result);
}
}
@Override
public void update(Observable observable, Object data) {
if (observable != connection) {
return;
}
// Your presentation logic goes here...
}
¿Cuál sería una forma buena / recomendada de vincular el ciclo de vida de la API de Google+ con el flujo de una aplicación de actividad múltiple? Hacer que las actividades dependan del método onConnected api client para activar su funcionalidad, usarlo como una única cosa de "activación", o tal vez algo completamente distinto.
Actualmente me cuesta entender cómo utilizar correctamente el inicio de sesión de Google+ en mi aplicación de Android, que tiene más de una actividad.
La idea es, en una primera fase, utilizar el inicio de sesión de G + solo para autenticar al usuario y poder recibir su correo electrónico, enviar notificaciones y cosas por el estilo. Finalmente, planeo desplegar otras funcionalidades de Google, como quizás Maps u otros servicios de Google Play, por lo que creo que es útil implementarlo ya.
Sin embargo, mi aplicación no se comporta como se esperaba, y he reducido el problema al hecho de que todavía no he entendido el inicio de sesión de G + en el ciclo de la aplicación cuando hay más de una actividad presente.
¿Cuál es la forma correcta o recomendada de implementar este método de autenticación? ¿Hay tal vez algún patrón que pueda guiarme en la dirección correcta?
Por ejemplo, he encontrado un diagrama muy simple del ciclo de vida del cliente de la API , pero ¿cómo se relaciona esto con el flujo de la aplicación?
Inicialmente tengo una actividad de inicio de sesión, donde coloco el botón de inicio de sesión. Siguiendo la guía de Google , puedo iniciar sesión y, cuando se llama al método onConnected, comienzo la actividad de inicio (algo así como el panel de control o la pantalla principal de la aplicación).
Esto funciona un poco. Por ejemplo, ¿cuál sería una buena manera de manejar onStart y onStop para cada actividad? ¿Debo volver a conectar y volver a autenticar el cliente de api cada vez que realizo cada actividad? Entonces tal vez sea una buena idea tener una BaseActivity para implementar todo esto.
Otro problema es, ¿debo usar el mismo objeto cliente de api y pasarlo de alguna manera, o tal vez almacenarlo en la clase de Actividad Base? ¿O debería estar creando e inicializando un nuevo objeto de cliente de API cada vez?
¿Qué tal si solo usamos la Actividad de inicio de sesión para autenticarse con G + y luego solo recibe el correo electrónico y lo almacena en una base de datos local, y marca al usuario como "autenticado" o "activo" o algo así? Eso evitaría que tenga que volver a autenticarme cada vez que se cierre la aplicación o se suspenda la conexión, incluso permitiendo algunos ahorros de batería.
La aplicación no está realmente usando la publicación de G + ni ninguna otra funcionalidad como esa. Idealmente, debería funcionar bien fuera de línea, y solo necesita conexión para cosas como la autenticación inicial u otras cosas únicas.
Cualquier sugerencia o punteros en la dirección correcta son muy apreciados.
Editar: He leído todas las guías y tutoriales que pude encontrar, que usan Google+, y cada uno de ellos aborda esto desde una perspectiva de actividad única. Creo que este es un problema lo suficientemente común como para beneficiarse de un patrón o al menos de una guía general.
Volver a conectar para cada actividad es absolutamente bien. En términos generales, hay 3 formas en que he visto personas que implementan esto:
- Implementar principalmente en una actividad básica, y hacer que los otros extiendan eso. Esto es conectar / desconectar en cada actividad, pero con el código en un solo lugar.
- Implemente la conexión / desconexión en un fragmento, e incluya eso en las actividades donde se necesita autenticación. Esto es útil si ya tiene una actividad básica que no puede extender (por ejemplo, algunos casos de juegos).
- Implementar un servicio para conectar / desconectar. Esto puede disparar un intento de emisión o similar si se requiere iniciar sesión.
Todos estos trabajos, y los he visto todos utilizados en aplicaciones del mundo real. Lo principal que debe recordar es separar la lógica del 99% (el usuario ha iniciado sesión o cerrado la sesión y se le informa de ello) del caso de uso relativamente raro de "iniciar sesión en este momento". Entonces, por ejemplo, es posible que haya fallado la activación de OnConnected / onConnection, pero en general está ignorando o simplemente cambiando un poco el estado de la aplicación. Solo en una pantalla con un botón de inicio de sesión, necesita la resolución de los resultados de la conexión y onActivityResult stuff. Piense en la conexión de los servicios de Google Play como si se tratara principalmente de preguntar por el estado del usuario, en lugar de iniciar sesión, y debería estar bien.
Estoy de acuerdo con la respuesta de Ian Barber, pero para explicar un poco más, sus Activity
deben considerarse en dos tipos: Activity
que resuelven el inicio de sesión y Activity
que requieren el inicio de sesión.
La mayoría de las Activity
no se preocupan de autenticar al usuario y tendrán la misma lógica en su aplicación. GoogleApiClient un GoogleApiClient , que se conecta al proceso de servicios de Google Play que se ejecuta en el dispositivo y lee el estado de inicio de sesión almacenado en caché del usuario. La mayoría de sus Activity
querrán restablecer el estado de su aplicación e iniciar su LoginActivity
inicio de LoginActivity
si el usuario no inició LoginActivity
. Cada Activity
debe mantener su propia instancia de GoogleApiClient
ya que es un objeto liviano utilizado para acceder al estado compartido de Google Play proceso de servicios. Este comportamiento podría, por ejemplo, estar encapsulado en una clase de BaseActivity
compartida o en una clase de SignInFragment
compartida, pero cada instancia debe tener su propia instancia de GoogleApiClient
.
Sin LoginActivity
su LoginActivity
debe implementarse de manera diferente. También debe crear un GoogleApiClient
, pero cuando recibe onConnected()
indica que el usuario ha onConnected()
, debe iniciar una Activity
apropiada para el usuario y finish()
. Cuando su LoginActivity
recibe onConnectionFailed()
indica que el usuario no ha LoginActivity
onConnectionFailed()
, debe intentar resolver los problemas de inicio de sesión con startResolutionForResult()
.