Разделяне на опасенията за дейността и GoogleApiClient

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

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

Има ли елегантен начин да поставите GoogleApiClient извън Activity?


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


Отговори (1)


0. TL;DR

За нетърпеливия програмист, работеща версия на следната реализация може да бъде намерена на GitHub.

Свеждайки нашия проблем само до концепцията за свързване, можем да приемем, че:

  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