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

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

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

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


person JP Ventura    schedule 30.07.2015    source источник


Ответы (1)


0. TL;DR

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

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

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

1. Образец состояния

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

Состояние из конечного автомата должно быть singleton, и самым простым способом сделать это в Java было создание Enum с именем 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) {}
}

Activity будет взаимодействовать с абстрактным классом 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. Одноэлементный шаблон

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

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 ;-)
    }
}
person JP Ventura    schedule 30.07.2015