Java Question Game Play Services

NFOBoy

Active Member
Licensed User
Longtime User
Erel, (or other very smart wrappers):

As you can tell in the request section, there is quite the interest for a Library for the Google Play Services Library, and I'm also on that list, but I know that someone coming along might take a while... so thought I might give it a go as I've been doing my Java pill swallowing for the last couple of months.

A few questions as I try to wrap my head around this.

I have my account up and running the demos, so I know I've got my setup in Eclipse and Play Developer Account working properly as per

https://github.com/playgameservices/android-samples and Setup Google Play Services SDK | Android Developers

Now, I've watched and done the video tutorial.. so have the "basics" for doing the xml and jar creation down.

After reviewing as many sample sources as I can find, and reading some of the other topics, want to make sure my approach is correct, and get some rudder steerage sooner rather than later.

There are two projects for use in Eclipse: the SDK library and then the BaseGameUtils.

BaseGameUtils has the very nice GameHelper and BaseGameActivity classes.
B4X:
public abstract class BaseGameActivity extends FragmentActivity implements
        GameHelper.GameHelperListener
B4X:
public class GameHelper implements GooglePlayServicesClient.ConnectionCallbacks,
        GooglePlayServicesClient.OnConnectionFailedListener, OnSignOutCompleteListener

(BaseGameActivity wraps the GameHelper to hide a lot of the stuff going on in there)
My assumptions:

As BaseGameUtils references the SDK, then the SDK is really what I want to focus on, since if I understand correctly, I don't think I can import BaseGameUtils (at least not the BaseGameActivity)

If I CAN, I would think exporting a JAR for both (minus the BaseGameActivity from the BaseGameUtils), and having @DependsOn for both would be ideal , and then I make my code to mimic the functionality of the of the BaseGameActivity to expose the methods to B4A.

If that sounds about right, or completely off base... please let me know!

Thanks,

Ross

P.S. Have found that I much, much more enjoy programming in B4A than Eclipse.. so thanks again so much Erel and all you power users that keep this such a fun and worthwhile effort)
 

NFOBoy

Active Member
Licensed User
Longtime User
Erel,

thanks so much. Will gladly put my efforts towards something else (even though I'm learning.. sloooowly) but I have managed to get the GameHelper Class to compile and (seems to) be recognized in my B4A library. Now I am trying to figure out how to implement the GameHelperListener... I look forward to the source so I can see how that works! (if I don't beat you to it... HAHAHAHA, yeah right)

Ross
 

NFOBoy

Active Member
Licensed User
Longtime User
Confused by "this"

So I am stuck at setting up the listener...

I understand using initialize and have this for exposing to B4A

B4X:
public void initialize(BA ba){
      mActivity = ba.activity;
      mHelper = new GameHelper(mActivity);
   }

which creates a new GameHelper object, and sets the mActivity for later use.

I also need to setup the listener which has two different methods in the Java class GameHelper:

B4X:
    /**
     * Same as calling @link{setup(GameHelperListener, int)} requesting only the
     * CLIENT_GAMES client.
     */
    public void setup(GameHelperListener listener) {
        setup(listener, CLIENT_GAMES);
    }

    /**
     * Performs setup on this GameHelper object. Call this from the onCreate()
     * method of your Activity. This will create the clients and do a few other
     * initialization tasks. Next, call @link{#onStart} from the onStart()
     * method of your Activity.
     *
     * @param listener The listener to be notified of sign-in events.
     * @param clientsToUse The clients to use. Use a combination of
     *            CLIENT_GAMES, CLIENT_PLUS and CLIENT_APPSTATE, or CLIENT_ALL
     *            to request all clients.
     */
    public void setup(GameHelperListener listener, int clientsToUse) {
        mListener = listener;
        mRequestedClients = clientsToUse;

        Vector<String> scopesVector = new Vector<String>();
        if (0 != (clientsToUse & CLIENT_GAMES)) {
            scopesVector.add(Scopes.GAMES);
        }
        if (0 != (clientsToUse & CLIENT_PLUS)) {
            scopesVector.add(Scopes.PLUS_LOGIN);
        }
        if (0 != (clientsToUse & CLIENT_APPSTATE)) {
            scopesVector.add(Scopes.APP_STATE);
        }

        mScopes = new String[scopesVector.size()];
        scopesVector.copyInto(mScopes);

        if (0 != (clientsToUse & CLIENT_GAMES)) {
            debugLog("onCreate: creating GamesClient");
            mGamesClient = new GamesClient.Builder(getContext(), this, this)
                    .setGravityForPopups(Gravity.TOP | Gravity.CENTER_HORIZONTAL)
                    .setScopes(mScopes)
                    .create();
        }

        if (0 != (clientsToUse & CLIENT_PLUS)) {
            debugLog("onCreate: creating GamesPlusClient");
            mPlusClient = new PlusClient.Builder(getContext(), this, this)
                    .setScopes(mScopes)
                    .build();
        }

        if (0 != (clientsToUse & CLIENT_APPSTATE)) {
            debugLog("onCreate: creating AppStateClient");
            mAppStateClient = new AppStateClient.Builder(getContext(), this, this)
                    .setScopes(mScopes)
                    .create();
        }
    }


setup is called from the OnCreate of the BaseGameActivity like this

B4X:
  @Override
    protected void onCreate(Bundle b) {
        super.onCreate(b);
        mHelper = new GameHelper(this);
        mHelper.setup(this, mRequestedClients);
    }

So I have tried mimicking this in my iniatilize by doing:

B4X:
public void initialize(BA ba){
      mActivity = ba.activity;
      mHelper = new GameHelper(mActivity);
      mHelper.setup(this, mRequestedClients);
   }

But, I get a null pointer exception when i initialize in B4A.

I have declared my class as:

B4X:
public class GameService implements GameHelper.GameHelperListener

and here's the BaseGameActivity declaration (which is what I'm trying to mimic)

B4X:
public abstract class BaseGameActivity extends FragmentActivity implements
        GameHelper.GameHelperListener

"this" in both cases should refer to the calling module.. in BaseGameActivity, I understand it is an Activity that implements the GameHelperListener

For GameService it should be a class that implements the GameHelperListener...

I "think" I want to handle this listeners inside my class, and use those to Raise Events in B4A, and if I understand correctly, my declaration should make the "this" call mean that it is also a GameHelperListener.

so I'm shaking my head, not sure where to point my brain... anyone care to enlighten my brain? :) (I am in a race with Erel as you can see by my earlier post...)

B4X:
package b4.pasagosoft.game.helper;


import android.app.Activity;
import anywheresoftware.b4a.BA;
import anywheresoftware.b4a.BA.DependsOn;
import anywheresoftware.b4a.BA.Permissions;
import anywheresoftware.b4a.BA.ShortName;

@Permissions(values = {}) // Permissions must always be declared otherwise your app or library won’t work.
@ShortName("GoogleGameService")
@DependsOn(values = { "googleplayservices", })

public class GameService implements GameHelper.GameHelperListener{
   
   protected Activity mActivity;
    // The game helper object. This class is mainly a wrapper around this object.
    protected GameHelper mHelper;


    // We expose these constants here because we don't want users of this class
    // to have to know about GameHelper at all.
    public static final int CLIENT_GAMES = GameHelper.CLIENT_GAMES;
    public static final int CLIENT_APPSTATE = GameHelper.CLIENT_APPSTATE;
    public static final int CLIENT_PLUS = GameHelper.CLIENT_PLUS;
    public static final int CLIENT_ALL = GameHelper.CLIENT_ALL;

    // Requested clients. By default, that's just the games client.
    protected int mRequestedClients = CLIENT_GAMES;


    /**
     * Constructs a BaseGameActivity with the requested clients.
     * @param requestedClients The requested clients (a combination of CLIENT_GAMES,
     *         CLIENT_PLUS and CLIENT_APPSTATE).
     */
    public void BaseGameServiceWithClients(int requestedClients) {

        setRequestedClients(requestedClients);
    }

    /**
     * Sets the requested clients. The preferred way to set the requested clients is
     * via the constructor, but this method is available if for some reason your code
     * cannot do this in the constructor. This must be called before onCreate in order to
     * have any effect. If called after onCreate, this method is a no-op.
     *
     * @param requestedClients A combination of the flags CLIENT_GAMES, CLIENT_PLUS
     *         and CLIENT_APPSTATE, or CLIENT_ALL to request all available clients.
     */
    protected void setRequestedClients(int requestedClients) {
        mRequestedClients = requestedClients;
    }
   
   @Override
   public void onSignInFailed() {
      // TODO Auto-generated method stub
      
   }

   @Override
   public void onSignInSucceeded() {
      // TODO Auto-generated method stub
      
   }
   
   public void initialize(BA ba){
      mActivity = ba.activity;
      mHelper = new GameHelper(mActivity);
      mHelper.setup(this, mRequestedClients);
   }

}

B4X:
package b4.pasagosoft.game.helper;

import java.util.Vector;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender.SendIntentException;
import android.os.Bundle;
import android.util.Log;
import android.view.Gravity;
import anywheresoftware.b4a.BA.DependsOn;
import anywheresoftware.b4a.BA.Permissions;
import anywheresoftware.b4a.BA.ShortName;

import com.google.android.gms.appstate.AppStateClient;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesClient;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.common.Scopes;
import com.google.android.gms.games.GamesClient;
import com.google.android.gms.games.OnSignOutCompleteListener;
import com.google.android.gms.games.multiplayer.Invitation;
import com.google.android.gms.plus.PlusClient;

@Permissions(values = {}) // Permissions must always be declared otherwise your app or library won’t work.
@ShortName("b4GameHelper")
@DependsOn(values = { "googleplayservices", })

public class GameHelper implements GooglePlayServicesClient.ConnectionCallbacks,
        GooglePlayServicesClient.OnConnectionFailedListener, OnSignOutCompleteListener {

    /** Listener for sign-in success or failure events. */
    public interface GameHelperListener {
        /**
         * Called when sign-in fails. As a result, a "Sign-In" button can be
         * shown to the user; when that button is clicked, call
         * @link{GamesHelper#beginUserInitiatedSignIn}. Note that not all calls to this
         * method mean an error; it may be a result of the fact that automatic
         * sign-in could not proceed because user interaction was required
         * (consent dialogs). So implementations of this method should NOT
         * display an error message unless a call to @link{GamesHelper#hasSignInError}
         * indicates that an error indeed occurred.
         */
        void onSignInFailed();

        /** Called when sign-in succeeds. */
        void onSignInSucceeded();
    }

    /**
     * The Activity we are bound to. We need to keep a reference to the Activity
     * because some games methods require an Activity (a Context won't do). We
     * are careful not to leak these references: we release them on onStop().
     */
    Activity mActivity = null;

    // OAuth scopes required for the clients. Initialized in setup().
    String mScopes[];

    // Request code we use when invoking other Activities to complete the
    // sign-in flow.
    final static int RC_RESOLVE = 9001;

    // Request code when invoking Activities whose result we don't care about.
    final static int RC_UNUSED = 9002;

    // Client objects we manage. If a given client is not enabled, it is null.
    GamesClient mGamesClient = null;
    PlusClient mPlusClient = null;
    AppStateClient mAppStateClient = null;

    // What clients we manage (OR-able values, can be combined as flags)
    public final static int CLIENT_NONE = 0x00;
    public final static int CLIENT_GAMES = 0x01;
    public final static int CLIENT_PLUS = 0x02;
    public final static int CLIENT_APPSTATE = 0x04;
    public final static int CLIENT_ALL = CLIENT_GAMES | CLIENT_PLUS | CLIENT_APPSTATE;

    // What clients were requested? (bit flags)
    int mRequestedClients = CLIENT_NONE;

    // What clients are currently connected? (bit flags)
    int mConnectedClients = CLIENT_NONE;

    // What client are we currently connecting?
    int mClientCurrentlyConnecting = CLIENT_NONE;

    // A progress dialog we show when we are trying to sign the user is
    ProgressDialog mProgressDialog = null;

    // Whether to automatically try to sign in on onStart().
    boolean mAutoSignIn = true;

    /*
     * Whether user has specifically requested that the sign-in process begin.
     * If mUserInitiatedSignIn is false, we're in the automatic sign-in attempt
     * that we try once the Activity is started -- if true, then the user has
     * already clicked a "Sign-In" button or something similar
     */
    boolean mUserInitiatedSignIn = false;

    // The connection result we got from our last attempt to sign-in.
    ConnectionResult mConnectionResult = null;

    // Whether our sign-in attempt resulted in an error. In this case,
    // mConnectionResult
    // indicates what was the error we failed to resolve.
    boolean mSignInError = false;

    // Whether we launched the sign-in dialog flow and therefore are expecting
    // an
    // onActivityResult with the result of that.
    boolean mExpectingActivityResult = false;

    // Are we signed in?
    boolean mSignedIn = false;

    // Print debug logs?
    boolean mDebugLog = false;
    String mDebugTag = "BaseGameActivity";

    // Messages (can be set by the developer).
    String mSigningInMessage = "";
    String mSigningOutMessage = "";
    String mUnknownErrorMessage = "Unknown error";

    // If we got an invitation id when we connected to the games client, it's
    // here.
    // Otherwise, it's null.
    String mInvitationId;

    // Listener
    GameHelperListener mListener = null;

    /**
     * Construct a GameHelper object, initially tied to the given Activity.
     * After constructing this object, call @link{setup} from the onCreate()
     * method of your Activity.
     */
    public GameHelper(Activity activity) {
        mActivity = activity;
    }
    
    public GameHelper() {
       
    }

    /** Sets the message that appears onscreen while signing in. */
    public void setSigningInMessage(String message) {
        mSigningInMessage = message;
    }

    /** Sets the message that appears onscreen while signing out. */
    public void setSigningOutMessage(String message) {
        mSigningOutMessage = message;
    }

    /**
     * Sets the message that appears onscreen when there is an unknown error
     * (rare!)
     */
    public void setUnknownErrorMessage(String message) {
        mUnknownErrorMessage = message;
    }

    /**
     * Same as calling @link{setup(GameHelperListener, int)} requesting only the
     * CLIENT_GAMES client.
     */
    public void setup(GameHelperListener listener) {
        setup(listener, CLIENT_GAMES);
    }

    /**
     * Performs setup on this GameHelper object. Call this from the onCreate()
     * method of your Activity. This will create the clients and do a few other
     * initialization tasks. Next, call @link{#onStart} from the onStart()
     * method of your Activity.
     *
     * @param listener The listener to be notified of sign-in events.
     * @param clientsToUse The clients to use. Use a combination of
     *            CLIENT_GAMES, CLIENT_PLUS and CLIENT_APPSTATE, or CLIENT_ALL
     *            to request all clients.
     */
    public void setup(GameHelperListener listener, int clientsToUse) {
        mListener = listener;
        mRequestedClients = clientsToUse;

        Vector<String> scopesVector = new Vector<String>();
        if (0 != (clientsToUse & CLIENT_GAMES)) {
            scopesVector.add(Scopes.GAMES);
        }
        if (0 != (clientsToUse & CLIENT_PLUS)) {
            scopesVector.add(Scopes.PLUS_LOGIN);
        }
        if (0 != (clientsToUse & CLIENT_APPSTATE)) {
            scopesVector.add(Scopes.APP_STATE);
        }

        mScopes = new String[scopesVector.size()];
        scopesVector.copyInto(mScopes);

        if (0 != (clientsToUse & CLIENT_GAMES)) {
            debugLog("onCreate: creating GamesClient");
            mGamesClient = new GamesClient.Builder(getContext(), this, this)
                    .setGravityForPopups(Gravity.TOP | Gravity.CENTER_HORIZONTAL)
                    .setScopes(mScopes)
                    .create();
        }

        if (0 != (clientsToUse & CLIENT_PLUS)) {
            debugLog("onCreate: creating GamesPlusClient");
            mPlusClient = new PlusClient.Builder(getContext(), this, this)
                    .setScopes(mScopes)
                    .build();
        }

        if (0 != (clientsToUse & CLIENT_APPSTATE)) {
            debugLog("onCreate: creating AppStateClient");
            mAppStateClient = new AppStateClient.Builder(getContext(), this, this)
                    .setScopes(mScopes)
                    .create();
        }
    }

    /**
     * Returns the GamesClient object. In order to call this method, you must have
     * called @link{setup} with a set of clients that includes CLIENT_GAMES.
     */
    public GamesClient getGamesClient() {
        if (mGamesClient == null) {
            throw new IllegalStateException("No GamesClient. Did you request it at setup?");
        }
        return mGamesClient;
    }

    /**
     * Returns the AppStateClient object. In order to call this method, you must have
     * called @link{#setup} with a set of clients that includes CLIENT_APPSTATE.
     */
    public AppStateClient getAppStateClient() {
        if (mAppStateClient == null) {
            throw new IllegalStateException("No AppStateClient. Did you request it at setup?");
        }
        return mAppStateClient;
    }

    /**
     * Returns the PlusClient object. In order to call this method, you must have
     * called @link{#setup} with a set of clients that includes CLIENT_PLUS.
     */
    public PlusClient getPlusClient() {
        if (mPlusClient == null) {
            throw new IllegalStateException("No PlusClient. Did you request it at setup?");
        }
        return mPlusClient;
    }

    /** Returns whether or not the user is signed in. */
    public boolean isSignedIn() {
        return mSignedIn;
    }

    /**
     * Returns whether or not there was a (non-recoverable) error during the
     * sign-in process.
     */
    public boolean hasSignInError() {
        return mSignInError;
    }

    /**
     * Returns the error that happened during the sign-in process, null if no
     * error occurred.
     */
    public ConnectionResult getSignInError() {
        return mSignInError ? mConnectionResult : null;
    }

    /** Call this method from your Activity's onStart(). */
    public void onStart(Activity act) {
        mActivity = act;

        debugLog("onStart.");
        if (mExpectingActivityResult) {
            // this Activity is starting because the UI flow we launched to
            // resolve a connection problem has just returned. In this case,
            // we should NOT automatically reconnect the client, since
            // onActivityResult will handle that.
            debugLog("onStart: won't connect because we're expecting activity result.");
        } else if (!mAutoSignIn) {
            // The user specifically signed out, so don't attempt to sign in
            // automatically. If the user wants to sign in, they will click
            // the sign-in button, at which point we will try to sign in.
            debugLog("onStart: not signing in because user specifically signed out.");
        } else {
            // Attempt to connect the clients.
            debugLog("onStart: connecting clients.");
            startConnections();
        }
    }

    /** Call this method from your Activity's onStop(). */
    public void onStop() {
        debugLog("onStop: disconnecting clients.");

        // disconnect the clients -- this is very important (prevents resource
        // leaks!)
        killConnections(CLIENT_ALL);

        // no longer signed in
        mSignedIn = false;
        mSignInError = false;

        // destroy progress dialog -- we create it again when needed
        dismissDialog();
        mProgressDialog = null;

        // let go of the Activity reference
        mActivity = null;
    }

    /** Convenience method to show an alert dialog. */
    public void showAlert(String title, String message) {
        (new AlertDialog.Builder(getContext())).setTitle(title).setMessage(message)
                .setNeutralButton(android.R.string.ok, null).create().show();
    }

    /** Convenience method to show an alert dialog. */
    public void showAlert(String message) {
        (new AlertDialog.Builder(getContext())).setMessage(message)
                .setNeutralButton(android.R.string.ok, null).create().show();
    }

    /**
     * Returns the invitation ID received through an invitation notification.
     * This should be called from your GameHelperListener's
     *
     * @link{GameHelperListener#onSignInSucceeded} method, to check if there's an
     * invitation available. In that case, accept the invitation.
     * @return The id of the invitation, or null if none was received.
     */
    public String getInvitationId() {
        return mInvitationId;
    }

    /** Enables debug logging, with the given logcat tag. */
    public void enableDebugLog(boolean enabled, String tag) {
        mDebugLog = enabled;
        mDebugTag = tag;
    }

    /**
     * Returns the current requested scopes. This is not valid until setup() has
     * been called.
     *
     * @return the requested scopes, including the oauth2: prefix
     */
    public String getScopes() {
        StringBuilder scopeStringBuilder = new StringBuilder();
        int clientsToUse = mRequestedClients;
        // GAMES implies PLUS_LOGIN
        if (0 != (clientsToUse & CLIENT_GAMES)) {
            addToScope(scopeStringBuilder, Scopes.GAMES);
        }
        if (0 != (clientsToUse & CLIENT_PLUS)) {
            addToScope(scopeStringBuilder, Scopes.PLUS_LOGIN);
        }
        if (0 != (clientsToUse & CLIENT_APPSTATE)) {
            addToScope(scopeStringBuilder, Scopes.APP_STATE);
        }
        return scopeStringBuilder.toString();
    }

    /** Sign out and disconnect from the APIs. */
    public void signOut() {
        mConnectionResult = null;
        mAutoSignIn = false;
        mSignedIn = false;
        mSignInError = false;

        if (mPlusClient != null && mPlusClient.isConnected()) {
            mPlusClient.clearDefaultAccount();
        }
        if (mGamesClient != null && mGamesClient.isConnected()) {
            showProgressDialog(false);
            mGamesClient.signOut(this);
        }

        // kill connects to all clients but games, which must remain
        // connected til we get onSignOutComplete()
        killConnections(CLIENT_ALL & ~CLIENT_GAMES);
    }

    /**
     * Handle activity result. Call this method from your Activity's
     * onActivityResult callback. If the activity result pertains to the sign-in
     * process, processes it appropriately.
     */
    public void onActivityResult(int requestCode, int responseCode, Intent intent) {
        if (requestCode == RC_RESOLVE) {
            // We're coming back from an activity that was launched to resolve a
            // connection
            // problem. For example, the sign-in UI.
            mExpectingActivityResult = false;
            debugLog("onActivityResult, req " + requestCode + " response " + responseCode);
            if (responseCode == Activity.RESULT_OK) {
                // Ready to try to connect again.
                debugLog("responseCode == RESULT_OK. So connecting.");
                connectCurrentClient();
            } else {
                // Whatever the problem we were trying to solve, it was not
                // solved.
                // So give up and show an error message.
                debugLog("responseCode != RESULT_OK, so not reconnecting.");
                giveUp();
            }
        }
    }

    /**
     * Starts a user-initiated sign-in flow. This should be called when the user
     * clicks on a "Sign In" button. As a result, authentication/consent dialogs
     * may show up. At the end of the process, the GameHelperListener's
     * onSignInSucceeded() or onSignInFailed() methods will be called.
     */
    public void beginUserInitiatedSignIn() {
        if (mSignedIn)
            return; // nothing to do

        // reset the flag to sign in automatically on onStart() -- now a
        // wanted behavior
        mAutoSignIn = true;

        // Is Google Play services available?
        int result = GooglePlayServicesUtil.isGooglePlayServicesAvailable(getContext());
        debugLog("isGooglePlayServicesAvailable returned " + result);
        if (result != ConnectionResult.SUCCESS) {
            // Nope.
            debugLog("Google Play services not available. Show error dialog.");
            Dialog errorDialog = getErrorDialog(result);
            errorDialog.show();
            if (mListener != null)
                mListener.onSignInFailed();
            return;
        }

        mUserInitiatedSignIn = true;
        if (mConnectionResult != null) {
            // We have a pending connection result from a previous failure, so
            // start with that.
            debugLog("beginUserInitiatedSignIn: continuing pending sign-in flow.");
            showProgressDialog(true);
            resolveConnectionResult();
        } else {
            // We don't have a pending connection result, so start anew.
            debugLog("beginUserInitiatedSignIn: starting new sign-in flow.");
            startConnections();
        }
    }

    Context getContext() {
        return mActivity;
    }

    void addToScope(StringBuilder scopeStringBuilder, String scope) {
        if (scopeStringBuilder.length() == 0) {
            scopeStringBuilder.append("oauth2:");
        } else {
            scopeStringBuilder.append(" ");
        }
        scopeStringBuilder.append(scope);
    }

    void startConnections() {
        mConnectedClients = CLIENT_NONE;
        mInvitationId = null;
        connectNextClient();
    }

    void showProgressDialog(boolean signIn) {
        String message = signIn ? mSigningInMessage : mSigningOutMessage;

        if (mProgressDialog == null) {
            if (getContext() == null)
                return;
            mProgressDialog = new ProgressDialog(getContext());
        }

        mProgressDialog.setMessage(message == null ? "" : message);
        mProgressDialog.setIndeterminate(true);
        mProgressDialog.show();
    }

    void dismissDialog() {
        if (mProgressDialog != null)
            mProgressDialog.dismiss();
        mProgressDialog = null;
    }

    void connectNextClient() {
        // do we already have all the clients we need?
        int pendingClients = mRequestedClients & ~mConnectedClients;
        if (pendingClients == 0) {
            debugLog("All clients now connected. Sign-in successful.");
            succeedSignIn();
            return;
        }

        showProgressDialog(true);

        // which client should be the next one to connect?
        if (mGamesClient != null && (0 != (pendingClients & CLIENT_GAMES))) {
            debugLog("Connecting GamesClient.");
            mClientCurrentlyConnecting = CLIENT_GAMES;
        } else if (mPlusClient != null && (0 != (pendingClients & CLIENT_PLUS))) {
            debugLog("Connecting PlusClient.");
            mClientCurrentlyConnecting = CLIENT_PLUS;
        } else if (mAppStateClient != null && (0 != (pendingClients & CLIENT_APPSTATE))) {
            debugLog("Connecting AppStateClient.");
            mClientCurrentlyConnecting = CLIENT_APPSTATE;
        } else {
            throw new AssertionError("Not all clients connected, yet no one is next. R="
                    + mRequestedClients + ", C=" + mConnectedClients);
        }

        connectCurrentClient();
    }

    void connectCurrentClient() {
        switch (mClientCurrentlyConnecting) {
            case CLIENT_GAMES:
                mGamesClient.connect();
                break;
            case CLIENT_APPSTATE:
                mAppStateClient.connect();
                break;
            case CLIENT_PLUS:
                mPlusClient.connect();
                break;
        }
    }

    void killConnections(int whatClients) {
        if ((whatClients & CLIENT_GAMES) != 0 && mGamesClient != null
                && mGamesClient.isConnected()) {
            mConnectedClients &= ~CLIENT_GAMES;
            mGamesClient.disconnect();
        }
        if ((whatClients & CLIENT_PLUS) != 0 && mPlusClient != null
                && mPlusClient.isConnected()) {
            mConnectedClients &= ~CLIENT_PLUS;
            mPlusClient.disconnect();
        }
        if ((whatClients & CLIENT_APPSTATE) != 0 && mAppStateClient != null
                && mAppStateClient.isConnected()) {
            mConnectedClients &= ~CLIENT_APPSTATE;
            mAppStateClient.disconnect();
        }
    }

    public void reconnectClients(int whatClients) {
        showProgressDialog(true);

        if ((whatClients & CLIENT_GAMES) != 0 && mGamesClient != null
                && mGamesClient.isConnected()) {
            mConnectedClients &= ~CLIENT_GAMES;
            mGamesClient.reconnect();
        }
        if ((whatClients & CLIENT_APPSTATE) != 0 && mAppStateClient != null
                && mAppStateClient.isConnected()) {
            mConnectedClients &= ~CLIENT_APPSTATE;
            mAppStateClient.reconnect();
        }
        if ((whatClients & CLIENT_PLUS) != 0 && mPlusClient != null
                && mPlusClient.isConnected()) {
            mConnectedClients &= ~CLIENT_PLUS;
            mPlusClient.disconnect();
            mPlusClient.connect();
        }
    }

    /** Called when we successfully obtain a connection to a client. */
    @Override
    public void onConnected(Bundle connectionHint) {
        debugLog("onConnected: connected! client=" + mClientCurrentlyConnecting);

        // Mark the current client as connected
        mConnectedClients |= mClientCurrentlyConnecting;

        // If this was the games client and it came with an invite, store it for
        // later retrieval.
        if (mClientCurrentlyConnecting == CLIENT_GAMES && connectionHint != null) {
            debugLog("onConnected: connection hint provided. Checking for invite.");
            Invitation inv = connectionHint.getParcelable(GamesClient.EXTRA_INVITATION);
            if (inv != null && inv.getInvitationId() != null) {
                // accept invitation
                debugLog("onConnected: connection hint has a room invite!");
                mInvitationId = inv.getInvitationId();
                debugLog("Invitation ID: " + mInvitationId);
            }
        }

        // connect the next client in line, if any.
        connectNextClient();
    }

    void succeedSignIn() {
        debugLog("All requested clients connected. Sign-in succeeded!");
        mSignedIn = true;
        mSignInError = false;
        mAutoSignIn = true;
        mUserInitiatedSignIn = false;
        dismissDialog();
        if (mListener != null) {
            mListener.onSignInSucceeded();
        }
    }

    /** Handles a connection failure reported by a client. */
    @Override
    public void onConnectionFailed(ConnectionResult result) {
        // save connection result for later reference
        mConnectionResult = result;
        debugLog("onConnectionFailed: result " + result.getErrorCode());
        dismissDialog();

        if (!mUserInitiatedSignIn) {
            // If the user didn't initiate the sign-in, we don't try to resolve
            // the connection problem automatically -- instead, we fail and wait
            // for the user to want to sign in. That way, they won't get an
            // authentication (or other) popup unless they are actively trying
            // to
            // sign in.
            debugLog("onConnectionFailed: since user didn't initiate sign-in, failing now.");
            mConnectionResult = result;
            if (mListener != null) {
                mListener.onSignInFailed();
            }
            return;
        }

        debugLog("onConnectionFailed: since user initiated sign-in, trying to resolve problem.");

        // Resolve the connection result. This usually means showing a dialog or
        // starting an Activity that will allow the user to give the appropriate
        // consents so that sign-in can be successful.
        resolveConnectionResult();
    }

    /**
     * Attempts to resolve a connection failure. This will usually involve
     * starting a UI flow that lets the user give the appropriate consents
     * necessary for sign-in to work.
     */
    void resolveConnectionResult() {
        // Try to resolve the problem
        debugLog("resolveConnectionResult: trying to resolve result: " + mConnectionResult);
        if (mConnectionResult.hasResolution()) {
            // This problem can be fixed. So let's try to fix it.
            debugLog("result has resolution. Starting it.");
            try {
                // launch appropriate UI flow (which might, for example, be the
                // sign-in flow)
                mExpectingActivityResult = true;
                mConnectionResult.startResolutionForResult(mActivity, RC_RESOLVE);
            } catch (SendIntentException e) {
                // Try connecting again
                debugLog("SendIntentException.");
                connectCurrentClient();
            }
        } else {
            // It's not a problem what we can solve, so give up and show an
            // error.
            debugLog("resolveConnectionResult: result has no resolution. Giving up.");
            giveUp();
        }
    }

    /**
     * Give up on signing in due to an error. Shows the appropriate error
     * message to the user, using a standard error dialog as appropriate to the
     * cause of the error. That dialog will indicate to the user how the problem
     * can be solved (for example, re-enable Google Play Services, upgrade to a
     * new version, etc).
     */
    void giveUp() {
        mSignInError = true;
        mAutoSignIn = false;
        dismissDialog();
        debugLog("giveUp: giving up on connection. " +
                ((mConnectionResult == null) ? "(no connection result)" :
                        ("Status code: " + mConnectionResult.getErrorCode())));

        Dialog errorDialog = null;
        if (mConnectionResult != null) {
            // get error dialog for that specific problem
            errorDialog = getErrorDialog(mConnectionResult.getErrorCode());
            errorDialog.show();
            if (mListener != null) {
                mListener.onSignInFailed();
            }
        } else {
            // this is a bug
            Log.e("GameHelper", "giveUp() called with no mConnectionResult");
        }
    }

    /** Called when we are disconnected from a client. */
    @Override
    public void onDisconnected() {
        debugLog("onDisconnected.");
        mConnectionResult = null;
        mAutoSignIn = false;
        mSignedIn = false;
        mSignInError = false;
        mInvitationId = null;
        mConnectedClients = CLIENT_NONE;
        if (mListener != null) {
            mListener.onSignInFailed();
        }
    }

    /** Returns an error dialog that's appropriate for the given error code. */
    Dialog getErrorDialog(int errorCode) {
        debugLog("Making error dialog for error: " + errorCode);
        Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog(errorCode, mActivity,
                RC_UNUSED, null);

        if (errorDialog != null)
            return errorDialog;

        // as a last-resort, make a sad "unknown error" dialog.
        return (new AlertDialog.Builder(getContext())).setMessage(mUnknownErrorMessage)
                .setNeutralButton(android.R.string.ok, null).create();
    }

    void debugLog(String message) {
        if (mDebugLog)
            Log.d(mDebugTag, message);
    }

    @Override
    public void onSignOutComplete() {
        dismissDialog();
        if (mGamesClient.isConnected())
            mGamesClient.disconnect();
    }
}


Ross
 

NFOBoy

Active Member
Licensed User
Longtime User
Erel,

while I understand the fact that it may be difficult, I would not underestimate how important this would be for many that develop.

Almost any gaming application is instantly made more appealing when a social aspect is utilized. For those of us who would like to get some exposure for our apps, Nothing is better than "word-of-mouth", and the ability to brag amongst your friends is a powerful tool. (even if the application is an educational game)

I'm definitely not up to speed on everything that's needed to make the wrapper, as I'm not sure exactly how to debug something that can't be seen within the B4A debugger (and can't be run in the Eclipse console either), but I've been practicing with Java, and can see some possibility in developing in Java and accomplishing what I need to do there, but at a BIG cost in development time, which is why a lot of us got into B4A.

I know that a step by step guide might seem a bit too much, but as you did take a look at the library, can you tell me what you see are the steps to make the JAR files from the SDK and the BaseGameUtils?

I was having some issues, so I pulled the JAR file from the google play services SDK (I was getting some strange issues when trying to make the JAR from the lib), and followed the steps as outlined in your video to turn the GameHelper class into a JAR, but when I try and set the listener (as in my earlier post) I am getting a Null pointer when trying to call the code that sets the listener for GameHelper, and I don't know if the listener is being set incorrectly with "this", or what... (and with you mentioning that a wrapper might come out this week... I continued learning how to use the SurfaceHolder in Java, just to get some more experience)

I know it's a bit complex, but looking at the BaseGameUtils activity class, it doesn't look insurmountable, but there are some key concepts that I haven't quite managed to get entirely to make the wrapper, but do KNOW I would benefit, (and others as well) by not getting left in the dust by those developers that can bring the social aspect into their programs, so need to make a decision to continue going down the path of trying to make a Wrapper (I had gotten my hopes after your previous post, especially at looking at the source code, as implementing listeners between B4A and the code is still a mystery to me) or down the path of just going strictly Java. My guideline would be that a Week or Two of trying to get a wrapper built would be worth it... because I really, really want to take advantage of some great libraries that have already been put out here on B4A.

So, expect to see some more questions in this forum... as I try to Hack a working library for B4A, before I Hack my hands off (oh wait this isn't GoT's), before I give up and just try to go Java... (which will probably take a few years off my fun-meter)

Ross
 

NFOBoy

Active Member
Licensed User
Longtime User
ba.context vs ba.activity

Erel,

I found that my problem was with not declaring it an @ActivityObject, as the activity is being stored and then used by GameHelper to get the current context of the Activity.

Now then, if I change it to an Activity Object, then a new instance of the GoogleHelper object will have to be instantiated for each new activity, vs (if I understand correctly) I replace the Activity references with the ba.context, then it will be good throughout the life of all Activities if declared as a global?


(but then, I think I HAVE to make it an Activity Object, to correctly handle the implementation of GameHelper.Listener? )

Would making a class that runs on its own separate thread make sense in order to have it be instantiated once? (oh boy, I sense an Excedrin day, but at least now I can get it all to compile past the point of setting the listener)

Ross

(Does anybody else remember back to thinking "Man, finally mastered that binary tree sort, the rest of this ought to be cake!", and then starting your first day of compiler class and thinking "oops, to late to change majors now!")
 
Last edited:

Bas Hamstra

Member
Licensed User
Longtime User
I'm sorry but it will take some time till a library will be available. The configuration of this service is very complicated. I'm not yet convinced that it will be useful for many developers.

Hi Erel,

I am hoping and waiting for this too, for quite a while. If you have doubts about the interest, make a poll, or post a message with that question. I am sure the interest is huge. Advantages:

1. No need to maintain a (home) server that's ON all the time
2. No worries about server logic PHP, MySQL, complex messaging schemes
3. Easy Turnbased multiuser games, without messy implementation (as PHP, SQL, B4a, polling the poor homeserver like crazy by hundreds of devices)


Please consider how few turnbased multiuser games built with B4A there at the moment? That can CHANGE, consider the attention for B4A that generates...

Best regards,

Bas
 

NeoTechni

Well-Known Member
Licensed User
Longtime User
I'm sorry but it will take some time till a library will be available. The configuration of this service is very complicated. I'm not yet convinced that it will be useful for many developers.

I'd like to use it in my game.
 

tpakis

Active Member
Licensed User
Longtime User
+1 I think it's the future for android games, we must follow Google

Sent from my GT-P5110 using Tapatalk 4 Beta
 

ppgirl

Member
Licensed User
Longtime User
Hi Erel,

Google game service is very cool SDK for game develpment , It is easy to update personal game to internet game . please help porting B4A lib . we have many problem work on it.
 

Bas Hamstra

Member
Licensed User
Longtime User
Hi Erel,

Please please please can you give an update on this? Is it planned? Or is it somehow not compatible with the B4A archtitecture? I am very much looking forward to a solution to play simple 2 player games over the internet, without the need to set up a server. If it's not in the pipeline somehow, I would like to know that too, please.

(B4a is truly great compared to bloated and ultraslow Eclipse or even the new Android Studio. Simple things like "generate members" from the layout are still not in the newest Android Studio)

Kind regards,

Bas Hamstra





Hi Erel,

Google game service is very cool SDK for game develpment , It is easy to update personal game to internet game . please help porting B4A lib . we have many problem work on it.
 

Bas Hamstra

Member
Licensed User
Longtime User
Hi Erel,

With all the respect, and I fully understand the lib is difficult to wrap, but it is not difficult to use once it is in place. It handles all kinds of stuff like

- invitation handling (sending/accepting)
- notifications
- direct communication between players
- leaderboards
- save games in the cloud (so games can be restored after a disconnect)

and all of this via the web, not to mention management of "friends" etc (done in Google+). All of this this CAN be done via sql/php/networking perhaps also GCM but in the first place this is a LOT of work (really! I did part of it, set up my WAMPserver and figured out PHP/mySQL communication and got it working, but it is a such a big project in itself, that I stopped it, I rather work on the core of my app). I is certainly way harder than just using this lib, and more importantly: I would need to have a server running all the time.

I got NJDudes lib running to login to G+ succesfully, send and accept invites succesfully, but after that it crashes because mehod "OnP2PConnected" is not implementedin the lib. With 2 methods extra in place I expect heaven comes to earth.

Kind regards,

Bas
 

Computersmith64

Well-Known Member
Licensed User
Longtime User
Hi Bas,

I have been working on the source that NFOBoy kindly provided & have implemented the "onP2PConnected" method, as well as some other tweaks, like the ability to turn logging on/off via a property in B4A. Unfortunately, there are still some stability issues related to multiplayer & my Java skills aren't well developed enough for me to be able to instantly know the root cause(s) & fix them - so it's taking me some time to work through it. I'm reluctant to upload what I have done due to the stability issues, but can do so if everyone who wants to use it realizes that it's not perfect. Maybe we should set this one up as a kind of open source project & encourage those who have better Java skills than the rest of us to contribute to the development?

- Colin.
 

Bas Hamstra

Member
Licensed User
Longtime User
Wow, respect! I looked into the lib sources and could see OnP2PConnected/Disconnected was missing, but there it stopped for me.

You idea sound GREAT to me. It is also in line with NFOboys posts, he encourages building further on the lib as long as the befenits stay available to the community. Maybe you can upload the tweaked version with the clear disclaimer you wrote above? If we get the ball rolling, and get some working results, maybe that will encourage/motivate the lib gurus to assist? My main interest is to get a simple 2 player game going, for practice, to use the principles later for real. I am willing to test that, but know close to nothing about wrapping. Am playing a bit with Android Studio now (still bloated and slow plus I am pretty much a nono there).

Kind regards,

Bas
 

Computersmith64

Well-Known Member
Licensed User
Longtime User
OK - so here goes...

I have attached the .jar & .xml file in the GPlayServices zip. I renamed the lib to GPlayServices so that if I completely screwed it up, I could always go back to NFOBoy's version! Fyi - I have been using Eclipse (Kepler) to work with the .java files & Erel's Simple Library Compiler to generate the .jar & .xml files.

You need to download the latest google-play-services.jar (it's too big to upload here) using the Android SDK Manager (instructions here -> http://developer.android.com/google/play-services/setup.html) & put it in the B4A Libraries folder along with the GPlayServices.jar & GPlayServices.xml. With the latest google-play-services.jar, you also need to add this to your manifest:

B4X:
<meta-data android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />

& the corresponding line in your \Objects\res\values\ids.xml file:

B4X:
<integer name="google_play_services_version">4030500</integer>

Otherwise you will get an error when you try to run your app. Don't forget to set the ids.xml to read-only or it will get deleted when you build.

I have also attached the source (in the Gamehelper Source.zip) - note that this is my modified version of what NFOBoy shared a while back. The main things I did were:

- Add the onP2PConnected / onP2PDisconnected functions
- Add an .EnableDebugLogging property to gGoogleGameService & gRoomConfig (this will cause log messages to show up in the B4A log when you are running in debug mode)
- Add some debug messages in the .java files themselves (these should probably all be cleaned up if this wrapper is ever officially released)
- Fix a few errors / warnings in the java files - most of which were due to the new play services library implementation

So, I have been using the leaderboard & achievement functionality for a long time with NFOBoy's wrapper & once I added the onP2P... functions, I was able to successfully get 2 devices to send & accept invites & play a Yahtzee! game using a play services room. I did this on 2 devices that were next to each other (but connected through play services), then sent a test version to a tester I have in England (I am in the US). We were able to send each other invites, but never managed to get a game actually running. I don't know if this was because of some issue on his device, or something else - but as I said, it worked on 2 of my devices.

The biggest issue I have had is with stability. I find that if I connect to a room & start a game, then leave the room & try to reconnect, I often get a "Google Play Services has stopped" message on my device. When this happens, I either have to wait some time (30 minutes+) before it will work again, or go into application manager on my device & tap the "Clear Data" button on the Google Play Services app. I have tried all kinds of things to stop this crash happening, but so far not been able to. I have traced it to something that happens when trying to connect to a room after an initial connect / disconnect, but there is no error generated at all - just the crash on the device. I suspect it's something in the wrapper that is not exiting from a room "clean" & causing the issue when it tries to reconnect. The weird thing is that is happens way more consistently on my Galaxy S3 & Galaxy Tab 3 than it does on my Nexus 7 - in fact, Google Play Services on my Nexus 7 almost never crashes (but it does sometimes).

Apart from that, the wrapper seems to implement enough of the play services functionality to make it useful for leaderboards, achievements & multiplayer, but just needs to have the stability issue worked out. I'll still be trying to get it working properly, but in the meantime, anybody else is welcome to try...

- Colin.
 

Attachments

  • GPlayServices.zip
    100.2 KB · Views: 327
  • Gamehelper Source.zip
    34.3 KB · Views: 335

Bas Hamstra

Member
Licensed User
Longtime User
Hi Colin,

Sounds good! Speaking as a noob, have you tried to reproduce the error with 2 emulators set up, and then inspecting the logs from B4a? I say this because I too got the "Google play services has stopped" and the logs from B4a tracked down to the missing P2Pconnected method. So I am wondering if your case can be tracked down to a missing method the same way? It's a while ago, but I think I was able to inspect logs from B4a for BOTH emulators, with in the end of the trace "method xxx not implemented".

I am dying to try it out, but as an IT guy I am exploited like a slave these days and have to work overtime. I will try it out the coming weekend and see if I can get it running.

Kind regards,

Bas
 

Computersmith64

Well-Known Member
Licensed User
Longtime User
Hi Colin,

...have you tried to reproduce the error with 2 emulators set up, and then inspecting the logs from B4a? I say this because I too got the "Google play services has stopped" and the logs from B4a tracked down to the missing P2Pconnected method...

Bas

I haven't tried it with 2 emulators running, however I did try it with a release build on one device & a debug build on another & nothing came up in the B4A trace log. When I saw the issue with the missing method, I could see it in the trace. The other thing is that I put trace log statements all through the java code & can pinpoint exactly where the error occurs in the library - but it doesn't correlate to an executing piece of code. IIRC, it makes a call to RoomConfig.createRoom & then a few seconds later play services crashes on the device.

The fact that this exact same code works fine the first time on a "clean" run, but then crashes on a subsequent run makes me think that there is some kind of memory leak, or a conflict relating to trying to re-create a room that perhaps already exists - although, I have tried to work around that possibility by stopping & then re-initializing the game services client. According to the documentation, a room's lifecycle is tied to the client, so stopping the client & then re-initializing it should destroy the room & allow a new one to be created...
 

Computersmith64

Well-Known Member
Licensed User
Longtime User
So there was a new version of Google Play Services (v4.1.31) released in the last day or so. It hadn't been updated on my devices yet, but I was able to download the apk from here -> http://www.androidfilehost.com/?fid=23308990704255022, or you can wait until it gets updated automatically on your device(s).

I was hopeful that it would fix the Play Services crash on my Galaxy S3, but alas, it didn't. Interestingly, I am not seeing the issue at all on my Nexus 7 today (which is running v4.0.34).

- Colin.
 
Top