Как правильно использовать вход 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 способа реализации этого:
- Реализуйте в основном в основной деятельности, и пусть другие расширяют это. Это подключение / отключение в каждом действии, но с кодом только в одном месте.
- Реализуйте соединение / отключение во фрагменте и включите это в действия, где требуется авторизация. Это полезно, если у вас уже есть базовая активность, которую вы не можете расширить (например, в некоторых игровых случаях).
- Реализовать сервис для подключения / отключения. Это может вызвать трансляцию или подобное, если требуется вход в систему.
Все это работает, и я видел их все в реальных приложениях. Главное, что нужно помнить, - это отделить логику 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, я никогда не был доволен этим подходом.
Сводя нашу проблему только к концепции подключения, мы можем считать, что:
- Он скрывает клиент Google API.
- У него конечные состояния.
- Это (довольно) уникальный.
- Текущее состояние влияет на поведение приложения.
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...
}