B4A Library Google Play Game Services

This is a wrapper for the Google Play Services related to games. This library was tested successfully with the API 37 of Google Play Services.

List of features:
  • Sign-in: supported;
  • Achievements: supported;
  • Leaderboards: supported;
  • Real-time multiplayer: supported (NEW!);
  • Turn-based multiplayer: supported;
  • Level and XP: supported;
  • Gifts and requests: supported;
  • Events and quests: not supported;
  • Saved games: supported;
  • Notifications: supported;
  • Player stats: supported;
  • Nearby connections: supported.
To compile, you need B4A v6+, JDK v7+ and android.jar v21+ (cf. Tools/Configure paths).

The library is provided with its Java source code and a few B4A modules:
  • ClsConnection: manages the sign-in, sign-out and connection events;
  • CodConverter: converts your data map to and from the byte array needed for the Saved Games service (this module requires three extra libraries: JSON, ByteConverter and RandomAccessFile) in case you don't own the DataCollection library;
  • CodTurnBasedMatch: helper module for turn-based matches.
Before using this library, you need to:
  • download the Google Play services SDK and Android Support Repository with the Android SDK manager;
  • copy the GooglePlayGameServices library (Jar+Xml) to your additional B4A libraries folder.
When you create a project requiring this library, you need to:
  • add this line in the project attributes of your Main module:
    B4X:
    #AdditionalJar: com.google.android.gms:play-services-games
  • add also this line if you want to access the Nearby Connections API:
    B4X:
    #AdditionalJar: com.google.android.gms:play-services-nearby
  • add these lines to your manifest:
    B4X:
    AddApplicationText(
    <meta-data android:name="com.google.android.gms.games.APP_ID"
        android:value="@string/app_id" />
    <meta-data android:name="com.google.android.gms.version"
        android:value="@integer/google_play_services_version" />)
  • add also these lines to the manifest if you want to access the Nearby Connections API:
    B4X:
    AddApplicationText(
    <meta-data android:name="com.google.android.gms.nearby.connection.SERVICE_ID"
        android:value="$PACKAGE$" />)
  • copy the games-ids.xml file generated by your Google Play developer console in the Objects\res\values folder.
To generate the games-ids.xml file, click on the link at the bottom of some screens (e.g. achievements) in your developer console (you need of course a game project properly configured to see it):
ressources.png


This file should look like this:
B4X:
<?xml version="1.0" encoding="utf-8"?>
<!--
Google Play game services IDs.
Save this file as res/values/games-ids.xml in your project.
-->
<resources>
  <string name="app_id">01234567890</string>
  <string name="achievement_win_on_a_very_large_map">CgkIuqeG8-kOEAIQAQ</string>
  <string name="achievement_win_on_a_large_map">CgkIuqeG8-kOEAIQAg</string>
  <string name="achievement_win_on_a_medium_map">CgkIuqeG8-kOEAIQAw</string>
  <string name="achievement_win_on_a_small_map">CgkIuqeG8-kOEAIQBA</string>
  <string name="achievement_win_on_a_very_small_map">CgkIuqeG8-kOEAIQBQ</string>
  <string name="leaderboard_main_score">CgkIuqeG8-kOEAIQBw</string>
</resources>

To set up a game in the Google Play developer console, please read this, then this.

To get details about a returned status code, please read this.
 

Attachments

  • Demos_37.zip
    71 KB · Views: 571
  • GooglePlayGameServices_372.zip
    241.3 KB · Views: 568
Last edited:

Informatix

Expert
Licensed User
Longtime User
The code examples for this library work ok for a single activity app. Most apps are probably multiple activity apps.
??? The number of activities does not matter for this library.

To do this, I moved the following line from Activity_Create to Activity_Resume:
No, you should not move the initialization to Activity_resume because it is supposed to be called only once. To reconnect, it's better to use Connection.SignIn or Connection.AutoConnect depending on the case.
And if you need to sign in automatically, there's no need to call an OnClick event, call directly Connection.SignIn.

Additionally, I found the existing code in Activity_Resume did not work correctly when returning from another activity. Here is my code in Activity_Resume:
??? Unless you define the specific behavior that you expect, this is not true. The TurnBasedMatch example uses more than one activity (the Open inbox button and the Invite button open another activity) and works fine.

It is best to have the game-ids.xml generated automatically.
Why?
 

Jack Cole

Well-Known Member
Licensed User
Longtime User
Informatix, thank you for this library, and I am very sorry to have caused you irritation. If you could spare the time, please look at the sample app I have attached and see if you can determine where I went wrong. All I did was add a second activity. I copied all the code from the Main activity into Second. I added a button on the first activity to launch the second activity. I changed the targetSDK to 23.

On my devices, here is what happens.

Open the app. Tap Sign In. It signs in as expected. Open the second activity. Tap Sign In. It works as expected. Close the second activity. The first one does not reconnect. I tap Sign In and nothing happens. You can also get the problem by simply opening the second activity when you first launch the app and then close it. It won't sign in--in the first activity.
 

Attachments

  • ACHIEVEMENTS_AND_LEADERBOARDS.zip
    14.3 KB · Views: 304

Jack Cole

Well-Known Member
Licensed User
Longtime User
You can further see the problem I am experiencing by replacing connection.autoconnect with connection.signin in Activity_Resume. This is to try to have it sign in automatically. It works the first time through. After you open and close the second activity, it does not reconnect. The only way I could figure out to get around the problem was with the changes that I posted earlier. Hopefully you can figure out what I have done wrong.
 

Jack Cole

Well-Known Member
Licensed User
Longtime User
Why? You don't have to initialize the library or connect the player in each activity. Once is enough.

You can't access the services in the first activity from the second. You have to sign in for each activity that accesses the services.

Most of my apps have a main activity, but individual games are in different activities. I need to access leaderboards and so forth from the main activity, but also other activities.
 

Jack Cole

Well-Known Member
Licensed User
Longtime User
Why should I sign in twice?

I addition to what I noted above, you don't have to sign in twice to reproduce the problem. Just open the second activity and close it. It doesn't really matter what is in the second activity as far as the primary problem goes. If you open a second activity and close it, you become unable to sign in with the first activity. For whatever reason, this does not apply to activities from the Play Game Services, only other activities.
 

Informatix

Expert
Licensed User
Longtime User
You can't access the services in the first activity from the second. You have to sign in for each activity that accesses the services.
You cannot access the ClsConnection instance from another activity because of the GPlayConnection class in ClsConnection but it's not a real problem as the connection has to be established only once. The real problem is that if you set all events in the main activity, they won't be fired if you are in another activity so you have to redirect them to a service. For example, in the main activity:
B4X:
Connection.Initialize(True, False, False, Starter, "Connection_Success", "Connection_Failure", True, Debug)
In the Starter service:
B4X:
Sub Process_Globals
    Public Const SUCCESS_SIGNIN As Byte = 0
    Public Achievements As GPlayAchievements
    Public Leaderboards As GPlayLeaderboards
End Sub
Sub Connection_Success(Success As Byte)
    If Success = SUCCESS_SIGNIN Then
        'Initializes the Achievements and Leaderboards classes
        Achievements.Initialize
        Leaderboards.Initialize

        'Loads the list of achievements
        Achievements.Load(False).SetResultEvent("GP_onAchievementsLoaded")

        'Loads the list of leaderboards
        Leaderboards.LoadLeaderboardMetadata(False).SetResultEvent("GP_onLeaderboardsLoaded")
    End If
End Sub
Sub GP_onAchievementsLoaded(Result As Map)
    Dim StatusCode As Int = Result.Get(Achievements.RESULTMAP_STATUS_CODE)
etc...
 

Informatix

Expert
Licensed User
Longtime User
I addition to what I noted above, you don't have to sign in twice to reproduce the problem. Just open the second activity and close it. It doesn't really matter what is in the second activity as far as the primary problem goes. If you open a second activity and close it, you become unable to sign in with the first activity. For whatever reason, this does not apply to activities from the Play Game Services, only other activities.
Remove all code related to the library from the second activity and you will be able to sign in.
 

Computersmith64

Well-Known Member
Licensed User
Longtime User
Hi @Informatix - did you happen to implement the Rematch functionality in turn based games? I can't seem to find it anywhere in your demo app.

Thanks - Colin.
 

Informatix

Expert
Licensed User
Longtime User
Hi @Informatix - did you happen to implement the Rematch functionality in turn based games? I can't seem to find it anywhere in your demo app.

Thanks - Colin.
From the library documentation:
  • Rematch (MatchId As String) As GPlayPendingResult
    Creates a rematch of a previously completed turn-based match. The new match will have the same participants as the previous match. Note that only one rematch may be created from any single completed match, and only by a player that has already called FinishMatch on the match.
    MatchId: The ID of the previous match to re-create.
 

Computersmith64

Well-Known Member
Licensed User
Longtime User
From the library documentation:
Ahhhh - thanks! Sorry - I guess I wasn't looking in the right place. I was trying to find it under the GPlayTurnBasedMatch object instead of looking at the GPlayTurnBasedMultiPlayer object!

- Colin.
 

Computersmith64

Well-Known Member
Licensed User
Longtime User
From the library documentation:
I'm a little confused about a couple of things related to finishing a match (I'm testing with 2 players):

1) When a player calls FinishMatch2 their TBMatch.Status doesn't get updated to MATCH_STATUS_COMPLETE - but the other player's status does. This means that the player that called FinishMatch2 can't request a rematch. Is this intended behavior, or is it a bug in Google's GPGS SDK? I've seen a couple of questions about this behavior online, but they are quite old, so I would have thought if it was a bug it would've been fixed by now.

2) When the player whose status is set to MATCH_STATUS_COMPLETE (& TBMatch.CanRematch is set to true) calls Rematch (with the previous match id), the select players dialog appears. According to the documentation, the rematch should use the same players as the previous match, so I'm not sure why the select players dialog is appearing? Or am I missing something?

Thanks - Colin.
 

Informatix

Expert
Licensed User
Longtime User
1) When a player calls FinishMatch2 their TBMatch.Status doesn't get updated to MATCH_STATUS_COMPLETE - but the other player's status does. This means that the player that called FinishMatch2 can't request a rematch. Is this intended behavior, or is it a bug in Google's GPGS SDK? I've seen a couple of questions about this behavior online, but they are quite old, so I would have thought if it was a bug it would've been fixed by now.
I did not change rules, so this one is not a bug; it's how Google wants the things to be done.

2) When the player whose status is set to MATCH_STATUS_COMPLETE (& TBMatch.CanRematch is set to true) calls Rematch (with the previous match id), the select players dialog appears. According to the documentation, the rematch should use the same players as the previous match, so I'm not sure why the select players dialog is appearing? Or am I missing something?
In my demo, the select players dialog is only called by "btnInvite_Click". "Rematch" is not handled.
In your game the dialog has no reason to appear if you don't call "OpenSelectOpponentsDialog".
 

Computersmith64

Well-Known Member
Licensed User
Longtime User
I did not change rules, so this one is not a bug; it's how Google wants the things to be done.
OK - I guess I'll have to live with it. I wasn't suggesting it was a bug in your code btw, but thought maybe it was something screwy in the SDK.


In my demo, the select players dialog is only called by "btnInvite_Click". "Rematch" is not handled.
In your game the dialog has no reason to appear if you don't call "OpenSelectOpponentsDialog".
Oh - yeah, I just realized that I do call OpenSelectOpponentsDialog when I'm setting up a new game - DOH!

Thanks for getting back to me! I'm slowly getting there...

- Colin.
 

Computersmith64

Well-Known Member
Licensed User
Longtime User
EDIT 2: After trying a few different things, I've come to the conclusion that most (if not all) of those callbacks have to be Activity based. Yes? If that's the case, how do I keep my GPlayRealTimeMultiplayer object alive when the Activity is in the background?

EDIT: OK - looking at the Connection.Initilize there is a Callback Module parameter - does this apply to all of the library callback functionality, or just for the Connection?

---------------------------------------------------------------------------------------------------
What determines where the listeners need to be implemented in RTMp? The problem I'm having is that if I receive a message (in onRealTimeMessageReceived) & the app is in the background, it will crash.

I thought I could work around it by putting all the callbacks into a service, but then I found that they don't get fired. I have also tried putting the multiplayer related variable declarations in a service, but that didn't stop the crash either.

- Colin.
 
Last edited:

Informatix

Expert
Licensed User
Longtime User
EDIT 2: After trying a few different things, I've come to the conclusion that most (if not all) of those callbacks have to be Activity based. Yes? If that's the case, how do I keep my GPlayRealTimeMultiplayer object alive when the Activity is in the background?

EDIT: OK - looking at the Connection.Initilize there is a Callback Module parameter - does this apply to all of the library callback functionality, or just for the Connection?

---------------------------------------------------------------------------------------------------
What determines where the listeners need to be implemented in RTMp? The problem I'm having is that if I receive a message (in onRealTimeMessageReceived) & the app is in the background, it will crash.

I thought I could work around it by putting all the callbacks into a service, but then I found that they don't get fired. I have also tried putting the multiplayer related variable declarations in a service, but that didn't stop the crash either.

- Colin.
Did you see the post above?
 

Computersmith64

Well-Known Member
Licensed User
Longtime User
EDIT: I went back & tried again by putting the GPlayRealTimeMultiplayer object & all the callbacks into a class, then initializing the class in my Main activity. Everything worked fine (I'm sure it didn't the first time I tried this!), however the main problem I have is that my views don't update when the Main activity is in the background. I guess this is normal behavior for an activity that's in the background, so I might have to look at a workaround.

I can use CallSubDelayed for the calls to subs in Main that handle the updating of the views, but I end up with too many queued events, so they start getting ignored.

I'm thinking that I should really be using turn-based multiplayer for this type of game anyway...

--------------------------------------------------------------------------------------------

Yes I did. I tried it & it worked fine for the Connection callbacks, but I couldn't get it to work for the real-time & turn-based multiplayer callbacks. I could see the callbacks being fired in the logs, but not in my code.

- Colin.
 
Last edited:

Schakalaka

Active Member
Licensed User
Longtime User
first i get this error
B4X:
AndroidManifest.xml:25: error: Error: No resource found that matches the given name (at 'value' with value '@string/app_id').

i try to remove above lines from manifest to run project ,then i get this error
B4X:
Initialize: creating a GoogleAPIClient instance
State change: Unconfigured -> Disconnected
Initialize: ready for connection
** Activity (main) Resume **
Connect: state = Disconnected
Connect: no auto sign-in, FirstAttempt is false
NetworkInfo.onConnectivityChange: Bundle[{networkInfo=NetworkInfo: type: WIFI[], state: CONNECTED/CONNECTED, reason: (unspecified), extra: (none), roaming: false, failover: false, isAvailable: true, inetCondition=0}]
Connect: state = Disconnected
Connect: first attempt
Connect: isGooglePlayServicesAvailable returned 0
State change: Disconnected -> Connecting
java.lang.IllegalStateException: A fatal developer error has occurred. Check the logs for further information.
    at com.google.android.gms.internal.hc$h.b(Unknown Source)
    at com.google.android.gms.internal.hc$h.d(Unknown Source)
    at com.google.android.gms.internal.hc$b.fq(Unknown Source)
    at com.google.android.gms.internal.hc$a.handleMessage(Unknown Source)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:137)
    at android.app.ActivityThread.main(ActivityThread.java:4517)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:511)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:993)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:760)
    at dalvik.system.NativeStart.main(Native Method)

Hello.
i have your same problem.
how have you fix it?
i need leaderboards
 

Computersmith64

Well-Known Member
Licensed User
Longtime User
Hello.
i have your same problem.
how have you fix it?
i need leaderboards
You need to go back & re-read the instructions at the start of this thread. You probably haven't created an ids.xml file in your project's \Objects\res\values folder. The file needs to look something like this:

<resources>
<!-- TODO: Replace this with your app's app ID! -->
<string name="app_id">[your app ID here]</string>
<integer name="google_play_services_version">[current GPGS version here]</integer>
</resources>

- Colin.
 
Top