Как правильно использовать вход Google Plus для нескольких действий?

Каков был бы хороший / рекомендуемый способ связать жизненный цикл клиента API api с потоком приложения с несколькими действиями? Сделать действия зависимыми от метода клиента onConnected api для запуска его функциональности, использовать его как единовременную вещь "активации" или, может быть, что-то еще полностью?

В настоящее время я пытаюсь понять, как правильно использовать вход в Google+ в моем приложении для Android, в котором есть несколько действий.

Идея состоит в том, чтобы на первом этапе использовать вход G+ только для аутентификации пользователя и получения его электронной почты, для отправки уведомлений и тому подобного. В конце концов я планирую развернуть другие функции Google, такие как, например, Карты или другие сервисы Google Play, поэтому я думаю, что уже полезно реализовать их.

Однако мое приложение работает не так, как ожидалось, и я сузил проблему до того факта, что я еще не понял цикл входа G+ в приложение, когда присутствует более одного действия.

Какой правильный или рекомендуемый способ реализации этого метода аутентификации? Есть ли какая-то модель, которая может направить меня в правильном направлении?

Например, я нашел очень простую диаграмму жизненного цикла клиента API, но как это связано с потоком приложений?

Изначально у меня есть вход в систему, где я помещаю кнопку входа. Следуя руководству Google, я могу войти в систему, и когда вызывается метод onConnected, я запускаю Домашнюю активность (что-то вроде панели инструментов или основного экрана приложения).

Это работает несколько. Например, что было бы хорошим способом обработки onStart и onStop для каждого действия? я должен повторно соединиться и повторно аутентифицировать клиент API каждый раз для каждого действия? Так что, возможно, это хорошая идея иметь BaseActivity для реализации всего этого.

Другая проблема заключается в том, должен ли я использовать тот же объект клиента API и каким-либо образом передавать его, или, возможно, сохранить его в классе Base Activity? или я должен каждый раз создавать и инициализировать новый клиентский объект API?

Как насчет того, чтобы просто использовать активность входа для аутентификации с помощью G+, а затем просто получить электронное письмо и сохранить его в локальной базе данных, а также пометить пользователя как "аутентифицированный" или "активный" или что-то в этом роде. Это избавит меня от необходимости повторной аутентификации каждый раз, когда приложение закрывается или соединение приостанавливается, даже учитывая некоторую экономию батареи.

Приложение на самом деле не использует сообщения G+ или другие подобные функции. В идеале он должен хорошо работать в автономном режиме, и ему нужно только соединение для таких вещей, как первоначальная аутентификация или другие единовременные вещи.

Любые предложения или указатели в правильном направлении очень ценятся.

Изменить: я прочитал все руководства и учебники, которые я могу найти, которые используют Google+, и каждый из них обращается к этому с одной точки зрения деятельности. Я думаю, что это достаточно распространенная проблема, которая выиграет от шаблона или хотя бы общего руководства.

3 ответа

Решение

Переподключение для каждого вида деятельности абсолютно нормально. В целом, я видел 3 способа реализации этого:

  1. Реализуйте в основном в основной деятельности, и пусть другие расширяют это. Это подключение / отключение в каждом действии, но с кодом только в одном месте.
  2. Реализуйте соединение / отключение во фрагменте и включите это в действия, где требуется авторизация. Это полезно, если у вас уже есть базовая активность, которую вы не можете расширить (например, в некоторых игровых случаях).
  3. Реализовать сервис для подключения / отключения. Это может вызвать трансляцию или подобное, если требуется вход в систему.

Все это работает, и я видел их все в реальных приложениях. Главное, что нужно помнить, - это отделить логику 99% (пользователь либо вошел в систему, либо вышел из системы, и вам сообщают об этом) от относительно редкого варианта использования "вход в данный момент". Так, например, у вас может быть много ошибок при запуске onConnected / onConnection, но в большинстве случаев вы игнорируете или просто немного изменяете состояние приложения. Только на экране с кнопкой входа в систему вам нужно разрешение результата подключения и onActivityResult. Подумайте о подключении к сервисам Google Play как о том, что вы спрашиваете о состоянии пользователя, а не входите в него, и у вас все будет хорошо.

Я согласен с ответом Яна Барбера, но, чтобы объяснить немного дальше, ваш Activity с следует рассматривать в двух типах - Activity s, которые решают войти в систему, и Activity s, которые требуют входа.

Наиболее Activity Они не занимаются аутентификацией пользователя и будут иметь ту же логику в вашем приложении. Они создадут GoogleApiClient, который подключается к процессу служб Google Play, запущенному на устройстве, и считывает состояние входа пользователя в кэш- память - возвращая onConnected() если пользователь вошел в систему, и onConnectionFailed() если не. Большинство ваших Activity s захочет сбросить состояние вашего приложения и запустить LoginActivity если пользователь не вошел в систему. Каждый Activity должен поддерживать свой собственный экземпляр GoogleApiClient поскольку это легкий объект, используемый для доступа к общему состоянию, которое хранится в процессе служб Google Play. Такое поведение может, например, быть заключено в общий BaseActivity класс или общий SignInFragment класс, но каждый экземпляр должен иметь свой собственный GoogleApiClient пример.

Ваш LoginActivity должно быть реализовано по-другому, однако. Следует также создать GoogleApiClient, но когда он получит onConnected() указывая, что пользователь вошел в систему, он должен начать соответствующий Activity для пользователя и finish(), Когда ваш LoginActivity получает onConnectionFailed() указав, что пользователь не вошел в систему, следует попытаться решить проблемы с startResolutionForResult(),

0. TL;DR

Для нетерпеливого кодера рабочую версию следующей реализации можно найти на GitHub.

После переписывания кода активности входа в систему несколько раз во многих различных приложениях, простым (и не очень элегантным) решением было создание клиента API Google в качестве объекта класса приложения. Но, поскольку состояние соединения влияет на поток UX, я никогда не был доволен этим подходом.

Сводя нашу проблему только к концепции подключения, мы можем считать, что:

  1. Он скрывает клиент Google API.
  2. У него конечные состояния.
  3. Это (довольно) уникальный.
  4. Текущее состояние влияет на поведение приложения.

1. Прокси шаблон

Так как Connection заключает в капсулу GoogleApiClient, он будет реализовывать 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();
    }
}

Действия могут общаться с классом Connection через методы connect, disconnect, а также revoke, но их поведение определяется текущим состоянием. Для конечного автомата требуются следующие методы:

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 Design Patterns описывается, как 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 методы connect, disconnect, а также 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...
}
Другие вопросы по тегам