Разделение проблем деятельности и GoogleApiClient

Как обычно, в моем LoginActivity есть много кода, и я бы предпочел отделить обязанности Activity от знака Google Play.

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

Есть ли элегантный способ размещения GoogleApiClient вне Activity ?

0. TL; DR

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

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

  1. Он имеет конечные состояния.
  2. Он инкапсулирует клиент подключения.
  3. Это (скорее) быть уникальным.
  4. Текущее состояние влияет на поведение приложения.

1. Государственный шаблон

Это поведенческий шаблон, позволяющий объекту изменять его поведение при изменении его внутреннего состояния. В книге шаблонов проектирования GoF описывается, как TCP-соединение может быть представлено этим шаблоном (что также является нашим делом).

Состояние из конечного автомата должно быть singleton , и проще всего сделать это на Java, чтобы создать Enum named State следующим образом:

 public enum State { CREATED { void connect(Connection connection) { connection.onSignUp(); } }, OPENING { void connect(Connection connection) { connection.onSignIn(); } }, OPENED { void disconnect(Connection connection) { connection.onSignOut(); } void revoke(Connection connection) { connection.onRevokeAndSignOut(); } }, CLOSED { void connect(Connection connection) { connection.onSignIn(); } }; void connect(Connection connection) {} void disconnect(Connection connection) {} void revoke(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); } 

2. Шаблон прокси-сервера

Класс GoogleConnection наследует от Connection и инкапсулирует GoogleApiClient , поэтому он должен предоставлять как ConnectionCallbacks и OnConnectionFailedListener следующим образом:

 @Override public void onConnected(Bundle connectionHint) { changeState(State.OPENED); } @Override public void onConnectionSuspended(int cause) { mGoogleApiClient.connect(); } @Override public void onConnectionFailed(ConnectionResult result) { if (state.equals(State.CLOSED) && result.hasResolution()) { changeState(State.CREATED); connectionResult = result; } else { connect(); } } public void onActivityResult(int resultCode) { if (resultCode == Activity.RESULT_OK) { connect(); } else { changeState(State.CREATED); } } 

На втором этапе этого объяснения требуются методы onSignIn() , onSignUp() , onSignOut() и onRevokeAndSignOut .

 public void onSignUp() { try { Activity activity = activityWeakReference.get(); changeState(State.OPENING); connectionResult.startResolutionForResult(activity, REQUEST_CODE); } catch (IntentSender.SendIntentException e) { changeState(State.CREATED); mGoogleApiClient.connect(); } } public void onSignIn() { if (!mGoogleApiClient.isConnected() && !mGoogleApiClient.isConnecting()) { mGoogleApiClient.connect(); } } public void onSignOut() { Plus.AccountApi.clearDefaultAccount(mGoogleApiClient); mGoogleApiClient.disconnect(); changeState(State.CLOSED); mGoogleApiClient.connect(); } public void onRevokeAndSignOut() { Plus.AccountApi.clearDefaultAccount(mGoogleApiClient); Plus.AccountApi.revokeAccessAndDisconnect(mGoogleApiClient); changeState(State.CLOSED); mGoogleApiClient = mGoogleApiClientBuilder.build(); mGoogleApiClient.connect(); } 

3. Шаблон Singleton

Поскольку нет необходимости повторно создавать этот класс повторно, мы предоставляем его как singleton:

 public static Connection getInstance(Activity activity) { if (null == sConnection) { sConnection = new GoogleConnection(activity); } return sConnection; } public void onActivityResult(int result) { if (result == Activity.RESULT_OK) { changeState(State.CREATED); } else { changeState(State.CLOSED); } onSignIn(); } private GoogleConnection(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; googleApiClient.connect(); } 

4. Наблюдаемый шаблон

Класс Connection расширяет Java Observable , поэтому один или многие действия могут наблюдать изменения состояния:

 @Override protected void onCreate(Bundle bundle) { mConnection = GoogleConnection.getInstance(this); mConnection.addObserver(this); } @Override protected void onDestroy() { mConnection.deleteObserver(this); } @Override protected void onActivityResult(int request, int result, Intent data) { if (Connection.REQUEST_CODE == request) { mConnection.onActivityResult(result); } } @Override public void update(Observable observable, Object data) { if (observable == mGoogleConnection) { // UI/UX magic happens here ;-) } }