web-dev-qa-db-fra.com

Comment utiliser correctement la connexion à Google Plus avec plusieurs activités?

Quelle serait une bonne façon/recommandée de lier le cycle de vie du client de l'API Google+ avec le flux d'une application multi-activités? Faire dépendre les activités de la méthode du client api onConnected pour déclencher sa fonctionnalité, l'utiliser comme une seule "activation" ou peut-être autre chose?

J'ai du mal à comprendre comment utiliser correctement la connexion Google+ dans mon Android, qui a plus d'une activité.

L'idée est, dans un premier temps, d'utiliser le signe G + juste pour authentifier l'utilisateur et pouvoir recevoir son email, envoyer des notifications et des trucs comme ça. Finalement, je prévois de déployer d'autres fonctionnalités de Google comme peut-être Maps ou d'autres services Google Play, donc je pense qu'il est déjà utile de les mettre en œuvre.

Cependant, mon application ne se comporte pas comme prévu, et j'ai limité le problème au fait que je n'ai pas encore compris le cycle de connexion G + dans l'application lorsque plusieurs activités sont présentes.

Quelle est la façon correcte ou recommandée d'implémenter cette méthode d'authentification? y a-t-il peut-être une sorte de schéma qui pourrait me guider dans la bonne direction?

Par exemple, j'ai trouvé n diagramme très simple du cycle de vie de le client api, mais comment cela se rapporte-t-il au flux de l'application?

Au départ, j'ai une activité de connexion, où je mets le bouton de connexion. Suivant guide de Google Je peux me connecter et lorsque la méthode onConnected est appelée, je démarre l'activité d'accueil (un peu comme le tableau de bord ou l'écran principal de l'application).

Cela fonctionne quelque peu. Par exemple, quelle serait une bonne façon de gérer les fonctions onStart et onStop pour chaque activité? dois-je me reconnecter et ré-authentifier le client api à chaque fois pour chaque activité? Alors peut-être que c'est une bonne idée d'avoir une BaseActivity pour implémenter tout cela.

Un autre problème est, dois-je utiliser le même objet client api et le transmettre d'une manière ou d'une autre, ou peut-être le stocker dans la classe d'activité de base? ou devrais-je créer et initialiser un nouvel objet client api à chaque fois?

Que diriez-vous d'utiliser simplement l'activité de connexion pour vous authentifier avec G +, puis récupérez simplement l'e-mail et stockez-le dans une base de données locale, et marquez l'utilisateur comme "authentifié" ou "actif" ou quelque chose. Cela m'éviterait de devoir me réauthentifier chaque fois que l'application est fermée ou que la connexion est suspendue, ce qui permet même d'économiser de la batterie.

L'application n'utilise pas vraiment la publication G + ou toute autre fonctionnalité comme celle-ci. Idéalement, cela devrait bien fonctionner hors ligne et ne nécessite une connexion que pour des choses comme l'authentification initiale ou d'autres choses uniques.

Toute suggestion ou indication dans la bonne direction est très appréciée.

Edit: j'ai lu tous les guides et tutoriels que je pouvais trouver, qui utilisent Google+, et chacun d'eux aborde cela dans une perspective d'activité unique. Je pense que c'est un problème suffisamment courant pour qu'il bénéficie d'un schéma ou au moins d'une ligne directrice générale.

42
Acapulco

Se reconnecter pour chaque activité est tout à fait correct. En gros, il y a 3 façons dont j'ai vu des gens mettre en œuvre ceci:

  1. Implémentez principalement dans une baseactivity, et demandez aux autres de l'étendre. Il s'agit de se connecter/déconnecter dans chaque activité, mais avec du code à un seul endroit.
  2. Implémentez la connexion/déconnexion dans un fragment et incluez-le dans les activités où l'authentification est nécessaire. Cela est utile si vous avez déjà une activité de base que vous ne pouvez pas étendre (par exemple, certains cas de jeux).
  3. Implémentez un service de connexion/déconnexion. Cela peut déclencher une diffusion ou similaire si une connexion est requise.

Tous ces travaux, et je les ai tous vus utilisés dans des applications du monde réel. La principale chose à retenir est de séparer la logique à 99% (l'utilisateur est connecté ou déconnecté, et vous en êtes informé) du cas d'utilisation relativement rare "se connecter à ce moment". Ainsi, par exemple, onConnect/onConnection peut avoir échoué à tirer beaucoup, mais la plupart du temps, vous ignorez ou inversez un peu l'état de l'application. Ce n'est que sur un écran avec un bouton de connexion que vous avez besoin de la résolution du résultat de la connexion et des éléments onActivityResult. Considérez la connexion aux services google play comme étant principalement de demander l'état de l'utilisateur, plutôt que de vous connecter, et ça devrait aller.

41
Ian Barber

Je suis d'accord avec la réponse d'Ian Barber mais pour expliquer un peu plus loin, vos Activity doivent être considérés en deux types - Activity qui résolvent la connexion et Activity qui nécessitent une connexion.

La plupart des Activity ne se soucient pas d'authentifier l'utilisateur et auront la même logique dans votre application. Ils créeront un GoogleApiClient , qui se connecte au processus des services Google Play en cours d'exécution sur l'appareil et lit l'état de connexion mis en cache de l'utilisateur - renvoyant onConnected() si l'utilisateur est signé dans, et onConnectionFailed() sinon. La plupart de vos Activity voudront réinitialiser l'état de votre application et démarrer votre LoginActivity si l'utilisateur n'était pas connecté. Chaque Activity devrait conserver sa propre instance de GoogleApiClient car il s'agit d'un objet léger utilisé pour accéder à l'état partagé détenu par le processus des services Google Play. Ce comportement peut, par exemple, être encapsulé dans une classe partagée BaseActivity ou une classe partagée SignInFragment, mais chaque instance doit avoir sa propre instance GoogleApiClient.

Cependant, votre LoginActivity doit être implémenté différemment. Il doit également créer un GoogleApiClient, mais lorsqu'il reçoit onConnected() indiquant que l'utilisateur est connecté, il doit démarrer un Activity approprié pour l'utilisateur et finish(). Lorsque votre LoginActivity reçoit onConnectionFailed() indiquant que l'utilisateur n'est pas connecté, vous devez essayer de résoudre les problèmes de connexion avec startResolutionForResult().

27
Lee

0. TL; DR

Pour le codeur impatient, une version de travail de l'implémentation suivante peut être trouvée sur GitHub .

Après avoir réécrit le code d'activité de connexion plusieurs fois dans de nombreuses applications différentes, la solution facile (et pas si élégante) a été de créer le client API Google en tant qu'objet de classe d'application. Mais, puisque l'état de connexion affecte le flux UX, je n'ai jamais été satisfait de cette approche.

En réduisant notre problème uniquement au concept de connexion , nous pouvons considérer que:

  1. Il cache le client Google API.
  2. Il a des états finis.
  3. C'est un (plutôt) unique.
  4. L'état actuel affecte le comportement de l'application.

1. Modèle de proxy

Puisque le Connection encapsule le GoogleApiClient, il implémentera les ConnectionCallbacks et 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();
    }
}

Les activités peuvent communiquer avec la classe Connection via les méthodes connect, disconnect et revoke, mais leurs comportements sont déterminés par l'état actuel. Les méthodes suivantes sont requises par la machine d'état:

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. Modèle d'état

Il s'agit d'un modèle de comportement qui permet à un objet de modifier son comportement lorsque son état interne change. Le livre GoF Design Patterns décrit comment une connexion TCP connexion peut être représentée par ce modèle (ce qui est également notre cas).

Un état d'une machine d'état doit être un singleton, et le plus simple est de le faire dans Java était de créer Enum nommé State comme suit:

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 classe Connection contient le contexte, c'est-à-dire l'état actuel, qui définit comment les méthodes Connectionconnect, disconnect et revoke seront se comporter:

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. Motif singleton

Puisqu'il n'est pas nécessaire de recréer cette classe à plusieurs reprises, nous la fournissons en tant que 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. Motif observable

La classe Connection étend Java Observable, donc 1 ou plusieurs activités peuvent observer les changements d'état:

@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...
}
10
JP Ventura