I'm need to get GCLID value inside my android app. What i found is Play Install Referrer Library, that returns me a utm tags, but i'm not sure that GCLID value will be inside val referrer = response.installReferrer
Code sample:
fun getGCLID(){
referrerClient = InstallReferrerClient.newBuilder(this).build()
referrerClient.startConnection(object : InstallReferrerStateListener {
override fun onInstallReferrerSetupFinished(responseCode: Int) {
when (responseCode) {
InstallReferrerClient.InstallReferrerResponse.OK -> {
// Connection established.
val response: ReferrerDetails = referrerClient.installReferrer
val referrer = response.installReferrer
val clickTimestamp = response.referrerClickTimestampSeconds
val installTimestamp = response.installBeginTimestampSeconds
Log.d("TagGCLID", "onInstallReferrerSetupFinished: 1")
if ("gclid" in referrer) {
Log.d("TagGCLID", "GCLID is detected in referrer // is $referrer")
//report to Firebase Analytics
} else {
Log.d("TagGCLID", "no GCLID is detected in referrer\n" +
"$referrer")
//do something else
}
}
InstallReferrerClient.InstallReferrerResponse.FEATURE_NOT_SUPPORTED -> {
Log.d("TagGCLID", "InstallReferrerResponse: -1")
// API not available on the current Play Store app.
}
InstallReferrerClient.InstallReferrerResponse.SERVICE_UNAVAILABLE -> {
Log.d("TagGCLID", "InstallReferrerResponse: -2")
// Connection couldn't be established.
}
}
}
override fun onInstallReferrerServiceDisconnected() {
Log.d("TagGCLID", "onInstallReferrerServiceDisconnected: -3")
// Try to restart the connection on the next request to
// Google Play by calling the startConnection() method.
}
})
}
What i'm suppose to do to get GCLID value?
yes, I also have a similar problem, I can get this gclid if the user has switched from the browser to the play market, but if the advertisement was clicked in the play market itself, then the gclid is not transmitted. Your method is written correctly, but it only works if the user goes through a browser.
Related
I have a situation where I want the livedata to be observed only once in the app. The problem is that I am working on the authentication for an app using some Node.js backend.
As I am sending the values to receive the response from the backend it's working fine till now. I observe that response and based on that I make changes to my fragment ( that is if the response received is true then move to next fragment, otherwise if it is false show a toast message ).
Now the problem is that :
Case 1: I opened the app, entered the right credentials and pressed the button, received true response from the server and goes to the next fragment.
Case 2: I opened the app, but entered the wrong credentials, I received a false from server and based on that the Toast is shown.
Case 3 (The issue): I opened the app, entered the wrong credentials and then without closing the fragment screen entered the right credentials by editing them, the app crashes and at the same time I receive multiple responses from the server via LiveData.
My observation: Looking more into that I found that the LiveData is attached to the fragment/activity and therefore it shows the last state. So as in case 3 the the last state was receiving the false value from backend it was used again and we were shown the error instead of going to the next screen.
Can anyone guide me how to solve this. Thanks
Some code that might be needed:
binding.btnContinue.setOnClickListener {
val number = binding.etMobileNumber.text.toString().toLong()
Timber.d("Number: $number")
authRiderViewModel.authDriver(number)
checkNumber()
}
Function which checks the number :
private fun checkNumber() {
authRiderViewModel.response.observe(viewLifecycleOwner, Observer {
Timber.d("Response: $it")
if (it!!.success == true) {
val action = LoginFragmentDirections.actionLoginFragmentToOtpFragment()
findNavController().navigate(action)
Timber.d("${it.message}")
} else {
Toast.makeText(requireContext(), "Number not registered", Toast.LENGTH_SHORT).show()
binding.etMobileNumber.setText("")
}
})
}
ViewModel code:
private val _response = MutableLiveData<AuthResponse>()
val response: LiveData<AuthResponse>
get() = _response
fun authDriver(number: Long) = viewModelScope.launch {
Timber.d("Number: $number")
myRepo.authDriver(number).let {
_response.postValue(it)
}
}
P.S I have tried using something called SingleLiveEvent but it doesn't seem to work.
I would create a separate class that tracks the UI state you need and update it when the state is consumed. Something like the following. I don't really know what the parameter is for authDriver, so this is a more generic example.
sealed interface AuthState {
object NotYetRequested: AuthState
object AwaitingResponse: AuthState
class ResponseReceived(val response: AuthResponse): AuthState {
var isHandled = false
private set
fun markHandled() {
isHandled = true
}
}
}
// In ViewModel:
private val _authState = MutableLiveData<AuthState>().also {
it.value = AuthState.NotYetRequested
}
val authState: LiveData<AuthState> get() = _authState
fun requestAuthentication() = viewModelScope.launch {
_authState.value = AuthState.AwaitingResponse
val response = myRepo.authenticate()
_authState.value = AuthState.ResponseReceived(response)
}
// In Fragment:
viewModel.authState.observe(viewLifecycleOwner) { authState ->
when (authState) {
AuthState.NotYetRequested -> ShowUiRequestingAuthentication()
AuthStateAwaitingResponse -> ShowIndeterminateProgressUi()
is AuthStateResponseReceived -> when {
authState.isHandled -> {} // do nothing? depends on your setup, might need to navigate to next screen if handled response is successful
authState.response.isSuccessful -> {
goToNextScreen()
authState.markHandled()
}
else -> {
showErrorToast()
ShowUiRequestingAuthentication()
authState.markHandled()
}
}
}
}
I am using Google Fit API to get fitness data for a kotlin app.
But API does not work in internal testing on Google Play Developer Console.
When connecting the USB cable and install the APK directly, it will succeed, so I think that the Google API setting(Sha1 Finger print,etc) is correct.
For the part that links with the API, the official github code is used as it is.
Do you have any information about my error?
/**
* This enum is used to define actions that can be performed after a successful sign in to Fit.
* One of these values is passed to the Fit sign-in, and returned in a successful callback, allowing
* subsequent execution of the desired action.
*/
enum class FitActionRequestCode {
SUBSCRIBE,
READ_DATA
}
/**
* This sample demonstrates combining the Recording API and History API of the Google Fit platform
* to record steps, and display the daily current step count. It also demonstrates how to
* authenticate a user with Google Play Services.
*/
class MainActivity : AppCompatActivity() {
private val fitnessOptions = FitnessOptions.builder()
.addDataType(DataType.TYPE_STEP_COUNT_CUMULATIVE)
.addDataType(DataType.TYPE_STEP_COUNT_DELTA)
.build()
private val runningQOrLater =
android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// This method sets up our custom logger, which will print all log messages to the device
// screen, as well as to adb logcat.
initializeLogging()
checkPermissionsAndRun(FitActionRequestCode.SUBSCRIBE)
}
private fun checkPermissionsAndRun(fitActionRequestCode: FitActionRequestCode) {
if (permissionApproved()) {
fitSignIn(fitActionRequestCode)
} else {
requestRuntimePermissions(fitActionRequestCode)
}
}
/**
* Checks that the user is signed in, and if so, executes the specified function. If the user is
* not signed in, initiates the sign in flow, specifying the post-sign in function to execute.
*
* #param requestCode The request code corresponding to the action to perform after sign in.
*/
private fun fitSignIn(requestCode: FitActionRequestCode) {
if (oAuthPermissionsApproved()) {
performActionForRequestCode(requestCode)
} else {
requestCode.let {
GoogleSignIn.requestPermissions(
this,
requestCode.ordinal,
getGoogleAccount(), fitnessOptions
)
}
}
}
/**
* Runs the desired method, based on the specified request code. The request code is typically
* passed to the Fit sign-in flow, and returned with the success callback. This allows the
* caller to specify which method, post-sign-in, should be called.
*
* #param requestCode The code corresponding to the action to perform.
*/
private fun performActionForRequestCode(requestCode: FitActionRequestCode) = when (requestCode) {
FitActionRequestCode.READ_DATA -> readData()
FitActionRequestCode.SUBSCRIBE -> subscribe()
}
var gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestEmail()
.build()
private fun oAuthPermissionsApproved() = GoogleSignIn.hasPermissions(
getGoogleAccount(),
fitnessOptions
)
/**
* Gets a Google account for use in creating the Fitness client. This is achieved by either
* using the last signed-in account, or if necessary, prompting the user to sign in.
* `getAccountForExtension` is recommended over `getLastSignedInAccount` as the latter can
* return `null` if there has been no sign in before.
*/
private fun getGoogleAccount() = GoogleSignIn.getAccountForExtension(this, fitnessOptions)
/**
* Handles the callback from the OAuth sign in flow, executing the post sign in function
*/
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (resultCode) {
RESULT_OK -> {
val postSignInAction = FitActionRequestCode.values()[requestCode]
postSignInAction.let {
performActionForRequestCode(postSignInAction)
}
}
else -> oAuthErrorMsg(requestCode, resultCode)
}
}
private fun oAuthErrorMsg(requestCode: Int, resultCode: Int) {
val message = """
There was an error signing into Fit. Check the troubleshooting section of the README
for potential issues.
Request code was: $requestCode
Result code was: $resultCode
""".trimIndent()
Log.e(TAG, message)
}
/** Records step data by requesting a subscription to background step data. */
private fun subscribe() {
// To create a subscription, invoke the Recording API. As soon as the subscription is
// active, fitness data will start recording.
Fitness.getRecordingClient(this, getGoogleAccount())
.subscribe(DataType.TYPE_STEP_COUNT_CUMULATIVE)
.addOnCompleteListener { task ->
if (task.isSuccessful) {
Log.i(TAG, "Successfully subscribed!")
} else {
Log.w(TAG, "There was a problem subscribing.", task.exception)
}
}
}
/**
* Reads the current daily step total, computed from midnight of the current day on the device's
* current timezone.
*/
private fun readData() {
Fitness.getHistoryClient(this, getGoogleAccount())
.readDailyTotal(DataType.TYPE_STEP_COUNT_DELTA)
.addOnSuccessListener { dataSet ->
val total = when {
dataSet.isEmpty -> 0
else -> dataSet.dataPoints.first().getValue(Field.FIELD_STEPS).asInt()
}
Log.i(TAG, "Total steps: $total")
}
.addOnFailureListener { e ->
Log.w(TAG, "There was a problem getting the step count.", e)
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Inflate the main; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.main, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val id = item.itemId
if (id == R.id.action_read_data) {
fitSignIn(FitActionRequestCode.READ_DATA)
return true
}
return super.onOptionsItemSelected(item)
}
/** Initializes a custom log class that outputs both to in-app targets and logcat. */
private fun initializeLogging() {
// Wraps Android's native log framework.
val logWrapper = LogWrapper()
// Using Log, front-end to the logging chain, emulates android.util.log method signatures.
Log.setLogNode(logWrapper)
// Filter strips out everything except the message text.
val msgFilter = MessageOnlyLogFilter()
logWrapper.next = msgFilter
// On screen logging via a customized TextView.
val logView = findViewById<View>(R.id.sample_logview) as LogView
TextViewCompat.setTextAppearance(logView, R.style.Log)
logView.setBackgroundColor(Color.WHITE)
msgFilter.next = logView
Log.i(TAG, "Ready")
}
private fun permissionApproved(): Boolean {
val approved = if (runningQOrLater) {
PackageManager.PERMISSION_GRANTED == ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACTIVITY_RECOGNITION
)
} else {
true
}
return approved
}
private fun requestRuntimePermissions(requestCode: FitActionRequestCode) {
val shouldProvideRationale =
ActivityCompat.shouldShowRequestPermissionRationale(
this,
Manifest.permission.ACTIVITY_RECOGNITION
)
// Provide an additional rationale to the user. This would happen if the user denied the
// request previously, but didn't check the "Don't ask again" checkbox.
requestCode.let {
if (shouldProvideRationale) {
Log.i(TAG, "Displaying permission rationale to provide additional context.")
Snackbar.make(
findViewById(R.id.main_activity_view),
R.string.permission_rationale,
Snackbar.LENGTH_INDEFINITE
)
.setAction(R.string.ok) {
// Request permission
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.ACTIVITY_RECOGNITION),
requestCode.ordinal
)
}
.show()
} else {
Log.i(TAG, "Requesting permission")
// Request permission. It's possible this can be auto answered if device policy
// sets the permission in a given state or the user denied the permission
// previously and checked "Never ask again".
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.ACTIVITY_RECOGNITION),
requestCode.ordinal
)
}
}
}
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<String>,
grantResults: IntArray
) {
when {
grantResults.isEmpty() -> {
// If user interaction was interrupted, the permission request
// is cancelled and you receive empty arrays.
Log.i(TAG, "User interaction was cancelled.")
}
grantResults[0] == PackageManager.PERMISSION_GRANTED -> {
// Permission was granted.
val fitActionRequestCode = FitActionRequestCode.values()[requestCode]
fitActionRequestCode.let {
fitSignIn(fitActionRequestCode)
}
}
else -> {
// Permission denied.
// In this Activity we've chosen to notify the user that they
// have rejected a core permission for the app since it makes the Activity useless.
// We're communicating this message in a Snackbar since this is a sample app, but
// core permissions would typically be best requested during a welcome-screen flow.
// Additionally, it is important to remember that a permission might have been
// rejected without asking the user for permission (device policy or "Never ask
// again" prompts). Therefore, a user interface affordance is typically implemented
// when permissions are denied. Otherwise, your app could appear unresponsive to
// touches or interactions which have required permissions.
}
}
}
}```
[1]: https://i.stack.imgur.com/UpQLO.png
We had the exact same issue and it doesn't seem to be documented anywhere. Even tho this question is over 1-year old I think it's nice to have it answered.
When you upload an APK for internal testing, it's not signed with your certificate, it's signed using a certificate issued by Google. For this reason, the SHA1 fingerprint configured for the fitness API does not match.
To overcome the issue, what you have to do is go to the developer console> internal testing > version details > downloads and download the apk from there. Once you have the apk, obtain the sha-1 fingerprint directly from it using
keytool -printcert -jarfile fileName.apk
That'll print the details of the certificate, including the SHA-1 fingerprint. Configure that fingerprint for the fitness oauth certificate and it'll work!
Remember to change this to the correct fingerprint when going to beta/production
It's nice to provide a closeup for this
I am trying to implement MSAL in Android for logging in user using their Microsoft credentials.
On clean install, first time I am able to get the token, and use it further for accessing Microsoft Graph API.
As expiry time for MSAL token is 1 hour by default, after 1 hour if I try to re-start the app, I face token authentication exception.
Now I am stuck on how to refresh the token again ?
In MSAL I have followed the examples, but nowhere is there any mention of refreshing a token using Android SDK [we can use API calls otherwise to get and refresh token, but I am not using API approach, I am using SDK for handling all the flow.]
I am trying to get through this for some days now.
private val AUTHORITY = "https://login.microsoftonline.com/common"
private var mSingleAccountApp: ISingleAccountPublicClientApplication? = null
private var mActiveAccount: MultiTenantAccount? = null
fun startTokenProcess(
activity: LoginActivity,
preferenceManager: PreferenceManager
) {
this.mActivity = activity
this.mPreferences = preferenceManager
mSingleAccountApp = null
// Creates a PublicClientApplication object with res/raw/auth_config.json
PublicClientApplication.createSingleAccountPublicClientApplication(activity,
R.raw.auth_config,
object : IPublicClientApplication.ISingleAccountApplicationCreatedListener {
override fun onCreated(application: ISingleAccountPublicClientApplication?) {
// initialization of ISingleAccountPublicClientApplication object
mSingleAccountApp = application
// check for existence of any account linked in cache
mSingleAccountApp?.getCurrentAccountAsync(object :
ISingleAccountPublicClientApplication.CurrentAccountCallback {
override fun onAccountLoaded(activeAccount: IAccount?) {
if (activeAccount == null) {
// nothing found
// start new interactive signin
mSingleAccountApp?.signIn(mActivity, "", getScopes(),
object : AuthenticationCallback {
override fun onSuccess(authenticationResult: IAuthenticationResult?) {
mActiveAccount =
authenticationResult?.account as MultiTenantAccount?
// save access token in SP
authenticationResult?.accessToken?.let {
mPreferences.putString(
KEY_ACCESS_TOKEN,
it
)
}
callGraphAPI(authenticationResult?.accessToken)
}
override fun onCancel() {
Timber.d("Canceled")
}
override fun onError(exception: MsalException?) {
Timber.d(exception?.errorCode)
}
})
} else {
// Founded an valid account in cache
// get account token from SP, call Graph API
// todo: check if access token expired ? ask for new token, clear SP
mActiveAccount = activeAccount as MultiTenantAccount?
val accessToken = mPreferences.getString(KEY_ACCESS_TOKEN)
if (accessToken != null) {
callGraphAPI(accessToken)
}
}
}
override fun onAccountChanged(
priorAccount: IAccount?,
currentAccount: IAccount?
) {
Timber.d("Founded an account $priorAccount")
Timber.d("Founded an account $currentAccount")
}
override fun onError(exception: MsalException) {
Timber.e(exception)
}
})
}
override fun onError(exception: MsalException?) {
Timber.e(exception)
}
})
}
I have tried to get token Silently and Interactively again, but no success.
SILENTLY:
mSingleAccountApp?.acquireTokenSilentAsync(getScopes(), AUTHORITY, getAuthSilentCallback())
private fun getAuthSilentCallback(): SilentAuthenticationCallback {
return object : SilentAuthenticationCallback {
override fun onSuccess(authenticationResult: IAuthenticationResult) {
Timber.d("Successfully authenticated")
/* Successfully got a token, use it to call a protected resource - MSGraph */
callGraphAPI(authenticationResult?.accessToken)
}
override fun onError(exception: MsalException) {
/* Failed to acquireToken */
Timber.e("Authentication failed: $exception")
if (exception is MsalClientException) {
Timber.e("Exception inside MSAL, more info inside MsalError.java ")
} else if (exception is MsalServiceException) {
Timber.e("Exception when communicating with the STS, likely config issue")
} else if (exception is MsalUiRequiredException) {
Timber.e("Tokens expired or no session, retry with interactive")
}
}
}
}
OR
INTERACTIVELY:
if (activeAccount == null) {
mSingleAccountApp?.signIn(mActivity, "", getScopes(),
object : AuthenticationCallback {
override fun onSuccess(authenticationResult: IAuthenticationResult?) {
mActiveAccount =
authenticationResult?.account as MultiTenantAccount?
// save access token in SP
authenticationResult?.accessToken?.let {
mPreferences.putString(
KEY_ACCESS_TOKEN,
it
)
}
callGraphAPI(authenticationResult?.accessToken)
}
override fun onCancel() {
Timber.d("Canceled")
}
override fun onError(exception: MsalException?) {
Timber.d(exception?.errorCode)
}
})
}
Edit 1:
Exceptions I am geting:
CoreHttpProvider[sendRequestInternal] - 414Graph service exception Error code: InvalidAuthenticationToken
CoreHttpProvider[sendRequestInternal] - 414Error message: Access token has expired.
CoreHttpProvider[sendRequestInternal] - 414SdkVersion : graph-java/v1.9.0
CoreHttpProvider[sendRequestInternal] - 414Authorization : Bearer eyJ0eXAiOiJKV1QiLCJub25jZSI[...]
CoreHttpProvider[sendRequestInternal] - 414Graph service exception Error code: InvalidAuthenticationToken
Throwable detail: com.microsoft.graph.http.GraphServiceException: Error code: InvalidAuthenticationToken
Error message: Access token has expired.
When I re-try to get token silently, I get the following exception:
l$getAuthSilentCallback: Authentication failed: com.microsoft.identity.client.exception.MsalServiceException: AADSTS700016: Application with identifier 'Some_ID' was not found in the directory 'Some_ID'. This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You may have sent your authentication request to the wrong tenant.
Trace ID: 'Some_ID'
Correlation ID: 'Some_ID'
Timestamp: 2020-08-15 06:06:11Z
getAuthSilentCallback: Exception when communicating with the STS, likely config issue
Edit 2
As per exceptions I was getting regarding config issue, I got the issue, it was related to the Authority URL which I was using. msal-client-application-configuration
DIAGNOSING
Are there any error details you can provide? And have you traced the HTTPS token refresh message?
WHAT IT SHOULD LOOK LIKE
The MSAL library should be sending a Refresh Token Grant message, as in steps 15 and 16 of my blog post.
My app uses AppAuth libraries but MSAL will work the same way, since that is standard for a mobile app.
I have implemented the Billing library in order to have in-app products in my app. I can make a successful purchase and everything works fine. If I try to make a purchase again, I get the response "Product already owned" which shows that the purchase is fine. I am using as productID the test id: "android.test.purchased"
Moreover, in the Main's Activity onResume() method, I call the queryPurchases() method in order to restore the product in case the user deletes the app and re-installs it. However, this method returns an empty purchases list although the response code is OK.
This is the way I am using the method:
private fun startServiceConnectionIfNeeded(executeOnSuccess: Runnable?) {
if (mBillingClient.isReady) {
executeOnSuccess?.run()
} else {
mBillingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(#BillingClient.BillingResponse billingResponse: Int) {
if (billingResponse == BillingClient.BillingResponse.OK) {
Log.i(TAG, "onBillingSetupFinished() response: $billingResponse")
executeOnSuccess?.run()
} else {
Log.w(TAG, "onBillingSetupFinished() error code: $billingResponse")
}
}
override fun onBillingServiceDisconnected() {
Log.w(TAG, "onBillingServiceDisconnected()")
}
})
}
}
// I call this method in Main's onResume()
fun updateBillingSharedPreferencesKey() {
val executeOnConnectedService = Runnable {
val purchasesResult = mBillingClient.queryPurchases(BillingClient.SkuType.INAPP)
val purchasesList = purchasesResult?.purchasesList
purchasesList?.let {
// this list is always empty, although I have a valid product
for (purchase in it) {
// I never enter this loop to handle the product functionality
Log.d(TAG, "purchase.sku = ${purchase?.sku}")
}
}
}
startServiceConnectionIfNeeded(executeOnConnectedService)
}
Am I doing something wrong? The service connection and the response code is ok.
How should I call the queryPurchases() method to get my valid in-app product?
I'm trying to connect my game to Google Play Games Services, but when I try to login, it always returns me an error code 8 (internal error).
The code is copy pasted from Google example:
lateinit var signInClient: GoogleSignInClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.layout_settings)
settings_login.setOnClickListener { login() }
signInClient = GoogleSignIn.getClient(this,
GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN).build()
)
}
private fun login() {
startActivityForResult(signInClient.signInIntent, 9001)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode != 9001) {
return
}
val task = GoogleSignIn.getSignedInAccountFromIntent(intent)
try {
val account = task.getResult(ApiException::class.java)
onConnected(account)
} catch (apiException: ApiException) {
var message: String? = apiException.message
if (message == null || message.isEmpty()) {
message = getString(R.string.signin_other_error)
}
onDisconnected()
AlertDialog.Builder(this)
.setMessage(message)
.setNeutralButton(android.R.string.ok, null)
.show()
}
}
In Google Play Console I've linked my game with debug keystore SHA-1.
I've checked everythin mentioned in Troubleshooting guide, but I still get this message again and again.
Does someone faced this issue? Any ideas how to debug it?
EDIT:
I found that it actually logs me in - if I restart game, method signInSilently() will be successful. However, it still shows this error 8 when I logout and try to log in manually. Could it be the problem with login activity overlay?
Oh, and I checked api access in Google Play Api Console - it shows that api actually receives my calls and it doesn't mention any errors.
EDIT 2: I've added requestEmail() to GoogleSignInOptions.Builder, and it shows me overlay with access request. However, it still fails in GoogleSignIn.getSignedInAccountFromIntent(intent).getResult(ApiException::class.java) with same error (8 - internal error).
It looks like this bug in Google Play Services 12.2.21:
https://github.com/googlesamples/google-services/issues/358
Google is supposed to be working on a fix for release over the air soon..
it's maybe late but I found the reason. 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
I didn't found the reason of this error, but I found an (ugly) workaround. I noticed, that when I restart game after manual login, even if there was this error, signInSilently() method works fine, which means that API actually authenticate me and fails later. So in catch block I'm checking for status code of error, and, if it's (8 - internal error), I'm requesting last signed in account. If account is present, I assume user to be logged in.
It's really dirty but I'm out of ideas.
//onActivityResult
val task = GoogleSignIn.getSignedInAccountFromIntent(intent)
try {
val account = task.getResult(ApiException::class.java)
onSuccess(account)
} catch (apiException: ApiException) {
val acc = GoogleSignIn.getLastSignedInAccount(context)
if (apiException.statusCode == 8 && acc != null && acc.email != null) {
onSuccess(account)
} else {
onFail(apiException)
}
}
I have a client with that error. Only ONE! With a Galaxy S9. Nothing happens when clicking on the Sign In button (startActivityForResult -> GoogleSignIn.getClient.getSignInIntent)