I'm trying to set up a Google OneTap SignIn button to my developed App (I'm not using Firebase to sign in) guiding by this source: https://developers.google.com/identity/one-tap/android/get-started
I've created both OAuth & Web credentials on Cloud Console. To generate OAuth Id I took SHA1 which was provided by Android Studio in signing-in report (I took develop SHA-1, but they are all the same anyway).
I've put to R.string.default_web_client_id the Client Id from WebAuth (Not an Android Id from OAuth).
As I use Firebase RTDB for storing some data, I set this SHA-1 there as well. The project name in Manifest, Cloud Console and Firebase Console are the same. From Firebase I downloaded "google-services.json" and put it in app root. On Firebase I also set valid service email.
This is how I implemented OneTap on login activity:
class LoginActivity : AppCompatActivity() {
...
private lateinit var oneTapClient: SignInClient
private lateinit var signUpRequest: BeginSignInRequest
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
oneTapClient = Identity.getSignInClient(this)
signUpRequest = BeginSignInRequest.builder()
.setPasswordRequestOptions(BeginSignInRequest.PasswordRequestOptions.builder()
.setSupported(false)
.build())
.setGoogleIdTokenRequestOptions(BeginSignInRequest.GoogleIdTokenRequestOptions.builder()
.setSupported(true)
// Your server's client ID, not your Android client ID.
.setServerClientId(getString(R.string.default_web_client_id))
// Only show accounts previously used to sign in.
.setFilterByAuthorizedAccounts(false)
.build())
// Automatically sign in when exactly one credential is retrieved.
.setAutoSelectEnabled(false)
.build()
}
// THIS CALLED FROM FRAGMENT WHEN GOOGLE BUTTON IS CLICKED
fun onGoogleClick() {
when (GoogleApiAvailability().isGooglePlayServicesAvailable(applicationContext)) {
0 -> {
beginGoogleSignIn()
}
1 -> {
toaster.show("Google services required")
}
2 -> {
toaster.show("Google services update required")
}
}
}
private fun beginGoogleSignIn() {
val tag = "$atag-beginGoogleSignIn"
oneTapClient.beginSignIn(signUpRequest)
.addOnSuccessListener(this) { result ->
try {
startIntentSenderForResult(
result.pendingIntent.intentSender, library.GOOGLE_SIGNIN_REQUEST_CODE,
null, 0, 0, 0)
} catch (e: IntentSender.SendIntentException) {
logg.d(atag, "Couldn't start One Tap UI: ${e.localizedMessage}")
}
}
.addOnFailureListener(this) { e ->
// No Google Accounts found. Just continue presenting the signed-out UI.
toaster.show("Please sign in to your google account on your phone")
logg.d(tag, e.localizedMessage)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
val tag = "$atag-onActivityResult"
super.onActivityResult(requestCode, resultCode, data)
// Result returned from launching the Intent from GoogleSignInClient.getSignInIntent(...);
if (requestCode == library.GOOGLE_SIGNIN_REQUEST_CODE) {
try {
val credential = oneTapClient.getSignInCredentialFromIntent(data)
token = credential.googleIdToken
when {
token != null -> {
// Got an ID token from Google. Use it to authenticate
// with your backend.
login = credential.id
name = credential.displayName
avatarUrl = credential.profilePictureUri.toString()
tryToLogin()
}
else -> {
// Shouldn't happen.
logg.d(atag, "No ID token!")
}
}
} catch (e: ApiException) {
when (e.statusCode) {
CommonStatusCodes.CANCELED -> {
toaster.show("Please sign in to your google account on your phone")
logg.d(tag, "One-tap dialog was closed.")
// Don't re-prompt the user.
}
CommonStatusCodes.NETWORK_ERROR -> {
logg.d(tag, "One-tap encountered a network error.")
// Try again or just ignore.
}
else -> {
logg.d(tag, "Couldn't get credential from result." +
" (${e.localizedMessage})")
}
}
}
}
}
}
The main problem is that on some of AVD it works well, on other AVD id gives an Error:
16: Cannot find a matching credential.
However on this AVD Google Services is up to date, and user is logged in to Google Play
On real device I got this error:
10: Caller not whitelisted to call this API.
Google services is also up to date here and user is logged in to Play Store.
Everywhere I used my real gmail address.
What can be wrong?
UPDATE
Spending a lot of time trying to solve this I have just figured out that OneTap is working on some devices, and is not working on other. Also tried to re-create credentials several times.
Not having very much time to solve this I just use both OneTap way and alternate way to sign in with Google credentials.
// In activity
lateinit var oneTapClient: SignInClient
lateinit var signUpRequest: BeginSignInRequest
private lateinit var gso: GoogleSignInOptions
lateinit var mGoogleSignInClient: GoogleSignInClient
private fun initApp() {
oneTapClient = Identity.getSignInClient(this)
signUpRequest = BeginSignInRequest.builder()
.setPasswordRequestOptions(
BeginSignInRequest.PasswordRequestOptions.builder()
.setSupported(false)
.build()
)
.setGoogleIdTokenRequestOptions(
BeginSignInRequest.GoogleIdTokenRequestOptions.builder()
.setSupported(true)
// Your server's client ID, not your Android client ID.
.setServerClientId(getString(R.string.default_web_client_id))
// Only show accounts previously used to sign in.
.setFilterByAuthorizedAccounts(false)
.build()
)
// Automatically sign in when exactly one credential is retrieved.
.setAutoSelectEnabled(false)
.build()
gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestProfile()
.requestEmail()
.requestIdToken(getString(R.string.default_web_client_id))
.build()
mGoogleSignInClient = GoogleSignIn.getClient(this, gso)
}
// Somewhere else in fragment, 'parent' is reference to activity
// Call this when button clicked
private fun beginGoogleSignIn() {
parent.oneTapClient.beginSignIn(parent.signUpRequest)
.addOnSuccessListener(parent) { result ->
try {
startIntentSenderForResult(
result.pendingIntent.intentSender, state.library.GOOGLE_SIGNIN_REQUEST_CODE,
null, 0, 0, 0, null)
} catch (e: IntentSender.SendIntentException) {
stopSignIn("Couldn't start One Tap UI: ${e.localizedMessage}")
}
}
.addOnCanceledListener(parent) {
stopSignIn("Cancelled")
}
.addOnFailureListener(parent) { e ->
// Use alternate sign in
val signInIntent = parent.mGoogleSignInClient.signInIntent
startActivityForResult(signInIntent, state.library.GOOGLE_ALT_SIGNIN_REQUEST_CODE)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
// Result returned from launching the Intent from GoogleSignInClient.getSignInIntent(...);
if (requestCode == state.library.GOOGLE_SIGNIN_REQUEST_CODE) {
try {
handleOneTapSignIn(data)
} catch (e: ApiException) {
val message: String
when (e.statusCode) {
CommonStatusCodes.CANCELED -> {
message = "Please sign in to your google account on your phone"
// Don't re-prompt the user.
}
CommonStatusCodes.NETWORK_ERROR -> {
// Try again or just ignore.
message = "One-tap encountered a network error."
}
else -> {
message = "Couldn't get credential from result." +
" (${e.localizedMessage})"
}
}
stopSignIn(message)
}
} else if (requestCode == state.library.GOOGLE_ALT_SIGNIN_REQUEST_CODE) {
try {
handleAlternateSignIn(data)
} catch (e: ApiException) {
if (e.statusCode == 12501) {
// Dismissed
stopSignIn()
} else {
stopSignIn("${e.statusCode}")
}
}
}
}
private fun handleOneTapSignIn(data: Intent?) {
val credential = parent.oneTapClient.getSignInCredentialFromIntent(data)
val token = credential.googleIdToken
when {
token != null -> {
// Got an ID token from Google. Use it to authenticate
// with your backend.
login = credential.id
avatarUrl = credential.profilePictureUri?.toString() ?: ""
tryToLogin()
}
else -> {
// Shouldn't happen.
stopSignIn("No ID token!")
}
}
}
private fun handleAlternateSignIn(data: Intent?) {
val task = GoogleSignIn.getSignedInAccountFromIntent(data)
try {
val account = task.getResult(ApiException::class.java)
// Signed in successfully, show authenticated UI.
login = account.email!!
token = account.idToken ?: ""
avatarUrl = account.photoUrl?.toString() ?: ""
tryToLogin()
} catch (e: ApiException) {
// The ApiException status code indicates the detailed failure reason.
// Please refer to the GoogleSignInStatusCodes class reference for more information.
val message = when (e.statusCode) {
CommonStatusCodes.NETWORK_ERROR -> {
"Could not reach network"
}
else -> {
"SignIn failed with exception $e"
}
}
stopSignIn(message)
}
}
UPDATE 2
It seems 16: Cannot find a matching credential on Android Emulators is related not to user credentials attempting to sign in, but an app credential in Google Cloud Console.
The issue is not reproduced in my case when I filled consent page at Google Cloud Console as described at Get started with One Tap sign-in and sign-up
(I have left empty fields for links to EULA, etc.). Then I have changed package name (for another reasons), then double check that this package name was the same in both credentials configs on Google Cloud Console and in google-services.json. Also I checked that google-services.json is located under [project_root]/app directory.
I will check this on real device in a few days.