B4A Library [New] Google Play Games Services

Hi everyone,

This is a complete wrapper of the latest Google Play Games Services (v17.2.1). I made this for @Jack Cole and he allowed me to post it on the forum to help other users.

Though the functionalities are almost the same but the implementation is a little different than the old wrapper made by @Informatix . There are many methods that have been removed from the new version of GPGS libraries. For more details check this reference.

Important: The main difference is you cannot store the list of the achievements, events, leaderboards, scores, players and snapshots retrieved from their respective clients. When you want the list you have to call the load method to populate the buffer and after using the data you must have to release the buffer else you will get errors while accessing different classes. For example, if you fetch the achievement list and forgot to release the buffer and after that if you fetch the leaderboard list you will get an error.

The wrapper contains almost all (99%) the functionalities and methods that the new GPGS SDK has. Though not all the functionalities are tested. If you encounter any issue please post the complete log.

As the wapper has a huge number of functionalities it's not possible to publish the entire library reference here. I have attached an HTML file in the zip file for reference.

Classes:
  • GPGSAbstractDataBuffer
  • GPGSAchievement
  • GPGSAchievementsClient
  • GPGSEvent
  • GPGSEventsClient
  • GPGSGame
  • GPGSGames
  • GPGSGamesClient
  • GPGSGamesMetadataClient
  • GPGSLeaderboard
  • GPGSLeaderboardScore
  • GPGSLeaderboardVariant
  • GPGSLeaderboardsClient
  • GPGSNearbyConnections
  • GPGSNetworkInfo
  • GPGSPlayGamesService
  • GPGSPlayer
  • GPGSPlayerLevel
  • GPGSPlayerLevelInfo
  • GPGSPlayerStats
  • GPGSPlayerStatsClient
  • GPGSPlayersClient
  • GPGSScoreSubmissionData
  • GPGSScoreSubmissionDataResult
  • GPGSSnapshot
  • GPGSSnapshotContents
  • GPGSSnapshotMetadata
  • GPGSSnapshotMetadataChange
  • GPGSSnapshotsClient
  • GPGSUserAccount
  • GPGSVideoCapabilities
  • GPGSVideoCaptureState
  • GPGSVideosClient

Installation:
  1. Download latest IDE and Android SDK (Skip if already using the latest ones)
  2. Copy the attached jar and xml file to the additional libs folder.
  3. Open B4A SDK Manager and wait for the list to load.
  4. Deselect all.
  5. Only install these following libraries (installing other libraries may cause compilation error, do it at your own risk),
    1. firebase-abt
    2. play-services-games
    3. play-services-location
    4. play-services-tasks
  6. Now close the SDK Manager
  7. If you are going to integrate this to an existing project then remember to clean the project.
    IDE > Tools > Clean project.

Setup GPGS: Check this documentation to set up and configure the GPGS.

Manifest Code:
B4X:
'GAMES SERVICES
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" />
<activity android:name="com.google.android.gms.auth.api.signin.internal.SignInHubActivity" android:windowSoftInputMode="stateHidden|adjustPan"/>
)
'The xml file content generated from play console
CreateResource(values, game-ids.xml,  <?xml version="1.0" encoding="utf-8"?>
<resources>
  <string name="app_id" translatable="false">******</string>
  ....
</resources>
)
Usage:
Sign-in:
Sub Class_Globals
    Public GPlayGamesService As GPGSPlayGamesService 'This is the main entry point
    Public GGames As GPGSGames 'will be auto initialized on successful signin

    Public gc As GPGSGamesClient
    Public lbc As GPGSLeaderboardsClient
    Public pc As GPGSPlayersClient
    Public ac As GPGSAchievementsClient
End Sub

Private Sub B4XPage_Created (Root1 As B4XView)
    ...
    GPlayGamesService.Initialize("GPGS") 'this eventname prefix will be used for all the classes for this library.
    GPlayGamesService.SilentSignIn("",False)
End Sub

Sub GPGS_Connected
    Log($"GPGS_Connected"$)
    'After login get all the clients required from GPGSGames
    gc = GGames.GetGamesClient
    lbc = GGames.GetLeaderboardsClient
    pc = GGames.GetPlayersClient
    ac = GGames.GetAchievementsClient
End Sub

Sub GPGS_OnGPGSActivityResult(RequestCode As String, ResultCode As Int, ResultIntent As Intent) 
    If ResultCode = GPlayGamesService.RESULT_RECONNECT_REQUIRED Then
        'signed out from popup activity
        'internally it will clear the user login session so you dont have to call SignOut again.
        'you can signin again or do any other activity related to signout event
    End If
End Sub

Sub GPGS_SignInFailed(code As Int, status As String)
    Log($"GPGS_SignInFailed(code=${code}, status=${status})"$)
End Sub

Example of fetching achievement list and releasing the buffer after usage:
Private Sub Button_UnlockAchievement_Click
    ac.Load(False)
    Wait For GPGS_AchievementsLoaded(count As Int, statusCode As Int)
    'check if the process was successful or not
    If statusCode  = GPlayGamesService.RESULT_SUCCESS Then
        'the buffer will be accessible from ac.GetAchievementsBuffer
        Dim achd As GPGSAchievement = ac.GetAchievementFromBuffer(0)
        If achd.GetState = achd.STATE_HIDDEN Then
            ac.RevealImmediate(achd.GetAchievementId)
            ac.UnlockImmediate(achd.GetAchievementId)
        End If
        If achd.GetState = achd.TYPE_INCREMENTAL And achd.GetCurrentSteps<achd.GetTotalSteps Then
            ac.SetStepsImmediate(achd.GetAchievementId, achd.GetCurrentSteps+1)
        End If

        'Important: if you are done with the buffer then release it
        ac.GetAchievementsBuffer.Release '<- Important
    Else
        Log(GPlayGamesService.GetStatusCodeString(statusCode))
    End If
End Sub


Update 1.01: Fixed an issue related to the sign-out event triggered from popup activities (Achievements/Leaderboard) You can check the resultCode received from OnGPGSActivityResult event. If the resultCode = RESULT_RECONNECT_REQUIRED, then it means the user clicked on the sign-out button from the popup activities. Check the example code.

Update 1.02: Added StatusCode to almost all the events to check whether the task was successful or not. Check the attached HTML file for event signature changes.

Update 1.03:
  1. Added IsSignedIn method to check if the user is signed in or not.
  2. Added GetLastSignedInAccount method that will return a GPGSUserAccount object or null if not signed in.
Update 1.04:
  1. Fixed IsSignedIn and GetLastSignedInAccount method where those were returning null when using SnapshotAPI.
  2. Fixed crashing issue while creating a snapshot with existing snapshot name. Update your project's SnapshotConflictOccurred event signature. It has been changed.
Update 1.05: Fixed unused permission issue when using GPGSGamesClient.
 

Attachments

  • GooglePlayGameService_v1.05.zip
    219.2 KB · Views: 268
Last edited:

coldteam

Active Member
Licensed User
Longtime User
Greetings!
First, thank you so much for your work and your generosity. @Jack Cole & @Biswajit
I decided to test how it works in my existing project in test modes.
B4X:
GPlayGamesService.SilentSignIn("",False)
This works first and I get the achievements and the high score table.
But then I clicked "Sign Out" in the highscore table interval. And when I restarted the app, it returns an error:
B4X:
GPGS_SignInFailed(code=4, status=SIGN_IN_REQUIRED)
This is normal behavior, because I have logged out of the account, and SilentSignIn does not know which account to enter.
Obviously, a login form is needed. Where the user selects an account and clicks login.

then i tried doing like this:
B4X:
GPlayGamesService.ManualSignIn("",False)
But this throws an error:
B4X:
GPGS_SignInFailed(code=8, status=INTERNAL_ERROR)

What do you advise?
 

Biswajit

Active Member
Licensed User
Longtime User
Check the first line of the ManualSignIn method comments.
B4X:
Performing interactive sign-in. Setup OAuth 2.0 client before using this method

Did you set up the OAuth client for your project?
 

coldteam

Active Member
Licensed User
Longtime User
Check the first line of the ManualSignIn method comments.
B4X:
Performing interactive sign-in. Setup OAuth 2.0 client before using this method

Did you set up the OAuth client for your project?

Yes, this is already a working project.
When I connect like this:
B4X:
GPlayGamesService.SilentSignIn("[email protected]",False)
It connects right away and I get the data I want.
 

coldteam

Active Member
Licensed User
Longtime User
I found a very similar description.

check this answer:
B4X:
It fixed in my case and I see your code has same problem.

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    ...

    val task = GoogleSignIn.getSignedInAccountFromIntent(intent) 
    ...
}

The intent you passed to the method getSignedInAccountFromIntent() is not the intent that returned by onActivityResult. The intent you passed come from activity, so you need to change it to

val task = GoogleSignIn.getSignedInAccountFromIntent(data)

"data" is the intent returned by onActivityResult
 

coldteam

Active Member
Licensed User
Longtime User
Check the first line of the ManualSignIn method comments.
B4X:
Performing interactive sign-in. Setup OAuth 2.0 client before using this method

Did you set up the OAuth client for your project?

Or does this phrase mean more than just setting up access to console.developers.google.com?
 

Biswajit

Active Member
Licensed User
Longtime User
Yes, this is already a working project.
When I connect like this:
B4X:
GPlayGamesService.SilentSignIn("[email protected]",False)
It connects right away and I get the data I want.
Ok checking.

I found a very similar description.
This isn't the case here. I doubled checked.

Or does this phrase mean more than just setting up access to console.developers.google.com?
Check this,
 

coldteam

Active Member
Licensed User
Longtime User
Yes, I insisted access accounts.

Doesn't the library use OAuth 2.0 when I connect like this?
B4X:
GPlayGamesService.SilentSignIn("[email protected]",False)

Or do I need to call something in the program before that?
B4X:
GPlayGamesService.ManualSignIn("",False)
 

Biswajit

Active Member
Licensed User
Longtime User
Doesn't the library use OAuth 2.0 when I connect like this?
This method fetches the already signed-in account and performs the login.

GPlayGamesService.ManualSignIn("",False)
This method shows an OAuth 2.0 consent screen where the user can see the application name and choose the account for signing in. You have to setup the consent screen and then create a credential to use this method. Check this link,
 

coldteam

Active Member
Licensed User
Longtime User
Now I went to the api console to the "OAuth consent screen" page And saw that confirmation is needed. You need to send for verification. But now my application works with the old library and users connect without problems. A window pops up in which you can select an account. How does it work then?
 

Biswajit

Active Member
Licensed User
Longtime User
Maybe that wasn't required for the older SDK. But now they have made this mandatory.
 

coldteam

Active Member
Licensed User
Longtime User
So I need to wait for the verification to complete and this should work?
 

Biswajit

Active Member
Licensed User
Longtime User
So I need to wait for the verification to complete and this should work?
Yes. (as per the documentation)
1615388606462.png
 

Biswajit

Active Member
Licensed User
Longtime User
I just found the issue. It wasn't working because we have to add the sign-in activity to the ApplicationManifest.xml file. Add this to your manifest file and it will work.
B4X:
AddApplicationText(<activity android:name="com.google.android.gms.auth.api.signin.internal.SignInHubActivity" android:windowSoftInputMode="stateHidden|adjustPan"/>)

I have added the full manifest code in the first post.
 
Last edited:

coldteam

Active Member
Licensed User
Longtime User
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" />
<activity
          android:name="com.google.android.gms.auth.api.signin.internal.SignInHubActivity"
android:screenOrientation="landscape"
android:windowSoftInputMode="stateAlwaysHidden|adjustPan" />
)

For landscape applications.
 

coldteam

Active Member
Licensed User
Longtime User
a couple of more nuances about the library.
While I am doing various tests with achievements. I noticed the following.
Choosing an achievement by number is not a good idea. The numbering of achievements in "ac.GetAchievementFromBuffer(0)" does not match the order in the Developer Console. The numbering depends on the specific player, depending on the previously opened achievements. with id = 0 I get achievement 13 from my list in the console.
It turns out to create a GPGSAchievement object. You need to load all the achievements and iterate over them until GetAchievementId is equal to the desired value.
Previously, to unlock an achievement, I did not check its status. I just did this:
B4X:
ac.UnlockImmediate(AchievementId)
or if you needed to show a hidden achievement
B4X:
ac.RevealImmediate(AchievementId)

it's faster than
B4X:
For i = 0 To count - 1
        Dim achd As GPGSAchievement = ac.GetAchievementFromBuffer(i)
        If achd.GetAchievementId = "achievement_id" Then
            If achd.GetState <> achd.STATE_UNLOCKED Then
                ac.UnlockImmediate(achd.GetAchievementId)
            End If
        End If
    Next

But when I unlocked my achievements, they were marked in the general list, but there was no pop-up item.
Which shows the unlocking of the achievement.
Does this need to be called separately?
 
Top