Stop the Google Play Games sign-in popup at launch: remove PlayGamesInitProvider
Here's a rule I hold every game to: no game shows a Google Play Games sheet when it opens. Nobody installs a puzzle or an idle game to be asked to sign in to something. The sheet covers the first thing they see, and some players will just close the app.
Play Games should start only when the player taps Achievements, Leaderboards or Cloud save, or silently in the background for someone who has connected before.
I thought I'd implemented that. Three games proved otherwise.
The JavaScript gate (necessary, not sufficient)
My games are Capacitor apps, and Play Games comes in through a plugin. The obvious approach is to call the plugin's initialize() only when the player taps one of those buttons, and I did exactly that in Genesis.
The sheet still appeared at launch.
The real cause: a content provider
play-services-games-v2, the Play Games library, ships an Android content provider called com.google.android.gms.games.provider.PlayGamesInitProvider. Android creates every content provider in an app's manifest when the process starts, before your Activity, before the WebView and before any of your JavaScript runs. That provider initialises the Play Games SDK, and the SDK may show its sign-in sheet.
So the JS gate does nothing. By the time your code decides not to initialise Play Games, it already has.
The fix
Remove the provider in your app's AndroidManifest.xml, inside <application>, using the manifest merger's remove instruction:
<provider android:name="com.google.android.gms.games.provider.PlayGamesInitProvider" android:authorities="${applicationId}.playgamesinitprovider" tools:node="remove" />
(Your root <manifest> element needs xmlns:tools="http://schemas.android.com/tools".)
With the provider gone, the SDK starts only when your code calls it.
Then the next problem: Connect did nothing
Initialising late created a new bug. In Genesis the Connect button did nothing for several taps. The Play Games SDK learns which Activity is in the foreground from lifecycle callbacks it registers when it starts. Start it late and it has missed the current Activity's lifecycle, so it has nothing to show its UI on.
The fix in Genesis was to briefly open an invisible Activity right after initialising, so the SDK sees a foreground Activity arrive. After that, sign-in and cloud save worked end to end: connect, clear the app's data, restore.
Two smaller lessons from the same week:
- A plugin proxy that hangs on
await. Returning a Capacitor plugin object from anasyncfunction makes JavaScript try to call.then()on it, which the proxy forwards to native code that never answers. Never return the raw plugin from a promise. - Never push a save on connect without asking. The first cloud-save version uploaded this device's save the moment you connected. On a new phone, that would overwrite the backup from your old one. Connect now asks: restore the cloud save, or keep this device's? Restore is never automatic. A bad push costs a cloud copy; a bad pull costs the game.
How it slipped through twice more
The provider removal lived in Genesis's build script. When Backpack Battle and Tavern Heroes added Play Games later, they copied the JavaScript gate from Genesis, not the manifest change. Both showed the sheet at launch. Sovereign turned out to have the same hole.
All three were fixed and released the same evening. The general lesson is a good one: when you copy a fix between projects, copy the native and manifest parts too, not just the code you can see.
The check that prevents it
Every game's build script now writes the provider removal into the manifest and refuses to build without it. To verify a built APK yourself:
aapt2 dump xmltree app-release.apk --file AndroidManifest.xml | grep PlayGamesInitProvider
It must print nothing.
One more Play Games gotcha: the OAuth consent screen
On Tavern Heroes, Play Games sign-in on an internal build failed with "Access blocked… has not completed the Google verification process". The Google Cloud project's OAuth consent screen was still in Testing mode with no test users. Publishing it needed branding details (home page, privacy policy, domain) filled in first.
A feature that needs setup in a console can be dead code even when the build is green. Test the tap itself on a real device, not just that the app boots.