web-dev-qa-db-ja.com

複数のアクティビティでGoogle Plusサインインを正しく使用するにはどうすればよいですか?

Google+ APIクライアントのライフサイクルをマルチアクティビティアプリのフローと結びつけるための良い/推奨される方法は何ですか?アクティビティをonConnected APIクライアントメソッドに依存させてその機能をトリガーするか、1回限りの「アクティブ化」として使用するか、それとも完全に別の何かとして使用するか

Androidアプリには複数のアクティビティがあるため、Google +サインインを正しく使用する方法を理解するのに苦労しています。

このアイデアは、最初のフェーズでは、ユーザーを認証し、メールを受け取ったり、通知やそのようなものを送信したりするためだけにG +サインインを使用することです。最終的にはマップや他のGoogle Playサービスのような他のGoogle機能をロールアウトする予定なので、すでに実装しておくと便利です。

ただし、私のアプリは期待どおりに動作しておらず、複数のアクティビティが存在する場合のアプリサイクルのG +サインインをまだ理解していないという事実に問題を絞り込みました。

この認証方法を実装する正しいまたは推奨される方法は何ですか?多分私を正しい方向に導くことができる種類のパターンはありますか?

たとえば、私は 非常に単純な図the api clientのライフサイクルを見つけましたが、これはアプリのフローとどのように関連していますか?

最初はログインアクティビティがあり、サインインボタンを配置しています。以下 Googleのガイド サインインでき、onConnectedメソッドが呼び出されると、ホームアクティビティ(ダッシュボードやアプリのメイン画面など)を開始します。

これはやや機能します。たとえば、各アクティビティのonStartとonStopを処理する良い方法は何でしょうか?すべてのアクティビティで毎回APIクライアントに再接続して再認証する必要がありますか?ですから、これをすべて実装するBaseActivityを用意するのは良い考えかもしれません。

別の問題は、同じAPIクライアントオブジェクトを使用してそれを何らかの方法で渡したり、ベースアクティビティクラスに保存したりする必要があるかどうかです。または毎回新しいAPIクライアントオブジェクトを作成して初期化する必要がありますか?

ログインアクティビティを使用してG +で認証し、メールを取得してローカルデータベースに保存し、ユーザーに「認証済み」または「アクティブ」などのフラグを立てるのはどうですか。これにより、アプリを閉じたり接続を一時停止したりするたびに再認証を行う必要がなくなり、バッテリーをいくらか節約することもできます。

アプリは、実際にはG +の投稿やその他の機能を使用していません。理想的にはオフラインでうまく機能し、初期認証やその他の1回限りの接続などが必要です。

正しい方向への提案やポインタは非常に高く評価されています。

編集:Google+を使用する、見つけたすべてのガイドとチュートリアルを読みました。それらすべてが、単一のアクティビティの観点からこれに対処しています。これは、パターンまたは少なくとも一般的なガイドラインの恩恵を受ける十分に一般的な問題だと思います。

42
Acapulco

アクティビティごとに再接続しても問題ありません。これを実装する人々について、私が見た方法には大きく3つあります。

  1. 主に基本アクティビティに実装し、他の人にそれを拡張してもらいます。これは、各アクティビティの接続/切断ですが、コードは1か所だけにあります。
  2. フラグメントに接続/切断を実装し、認証が必要なアクティビティにそれを含めます。これは、拡張できないbaseactivityがある場合に役立ちます(一部のゲームケースなど)。
  3. 接続/切断するサービスを実装します。サインインが必要な場合、これによりbroadcastintentまたは同様のイベントが発生する可能性があります。

これらすべてが機能し、実際にすべてのアプリで使用されているのを見てきました。覚えておくべき主なことは、99%のロジック(ユーザーがサインインまたはサインアウトしていて、そのことを通知されている)を、比較的まれな「現時点でサインインする」ユースケースから分離することです。したがって、たとえば、onConnected/onConnectionが何度も起動に失敗した可能性がありますが、ほとんどの場合、アプリケーションの状態を無視するか、少しだけフリップします。ログインボタンが表示されている画面でのみ、接続結果の解決とonActivityResultなどが必要になります。 Google Play開発者サービスの接続は、主にユーザーのログインではなく、ユーザーの状態を要求するものであると考えてください。問題はありません。

41
Ian Barber

Ian Barberの回答に同意しますが、もう少し詳しく説明すると、Activitysは、サインインを解決するActivitysと、サインインが必要なActivitysの2つのタイプで検討する必要があります。

ほとんどのActivitysはユーザーの認証に関係なく、アプリでも同じロジックを使用します。彼らは GoogleApiClient を作成します。これは、デバイスで実行されているGoogle Play開発者サービスプロセスに接続し、ユーザーのキャッシュされたサインイン状態を読み取ります。ユーザーがサインインしている場合はonConnected()を返します、そうでない場合はonConnectionFailed()。ほとんどのActivitysは、ユーザーがサインインしていない場合、アプリケーションの状態をリセットしてLoginActivityを開始する必要があります。各Activityは、独自のGoogleApiClientこれは、Google Play開発者サービスプロセスが保持する共有状態へのアクセスに使用される軽量オブジェクトであるためです。この動作は、たとえば、共有BaseActivityクラスまたは共有SignInFragmentクラスにカプセル化できますが、各インスタンスには独自のGoogleApiClientインスタンスが必要です。

ただし、LoginActivityは別の方法で実装する必要があります。 GoogleApiClientも作成する必要がありますが、ユーザーがサインインしていることを示すonConnected()を受信すると、ユーザーに適切なActivityを開始し、finish()LoginActivityonConnectionFailed()を受信して​​、ユーザーがサインインしていないことを示している場合は、startResolutionForResult()でサインインの問題を解決する必要があります。

27
Lee

0. TL; DR

せっかちなコーダーの場合、次の実装の作業バージョンは GitHub にあります。

多くの異なるアプリでログインアクティビティコードを何度か書き換えた後、簡単な(そしてそれほどエレガントではない)ソリューションは、アプリケーションクラスオブジェクトとしてGoogle APIクライアントを作成することでした。しかし、接続状態はUXフローに影響を与えるため、このアプローチには満足できませんでした。

問題を接続の概念に限定すると、次のように考えることができます。

  1. Google APIクライアントを非表示にします。
  2. 有限の状態があります。
  3. それは(むしろ)ユニークです。
  4. 現在の状態はアプリの動作に影響します。

1.プロキシパターン

ConnectionGoogleApiClientをカプセル化するため、ConnectionCallbacksおよび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();
    }
}

アクティビティは、メソッドconnectdisconnect、およびrevokeを介してConnectionクラスと通信できますが、動作は現在の状態によって決まります。ステートマシンには次のメソッドが必要です。

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.状態パターン

これは、内部状態が変化したときにオブジェクトがその動作を変更できるようにする動作パターンです。 GoFデザインパターンブック は、TCP接続をこのパターンで表現する方法を説明しています(これも私たちの場合です)。

状態マシンからの状態はsingletonである必要があり、Javaでそれを行う最も簡単な方法は、Enumという名前のStateを作成することでした次のように:

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

Connectionクラスは、Connectionメソッドconnectdisconnect、およびrevokeがどのように動作するかを定義するコンテキスト、つまり現在の状態を保持します振る舞う:

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.シングルトンパターン

このクラスを繰り返し作成する必要がないため、シングルトンとして提供します。

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.観察可能なパターン

ConnectionクラスはJava Observableを拡張するため、1つ以上のアクティビティで状態の変化を観察できます。

@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