I can't find resources on the internet on How to achieve the google API integration into an Compose based app.
I need help, especially in the AutoResolveHelper.resolveTask, how to do it in compose.
Thank you for your answers.
(That's mind blowing that there is no more documentation on this API, it's pretty difficult to implement).
Edit :
This is the traditional way to do it ->
private fun requestPayment() {
// Disables the button to prevent multiple clicks.
googlePayButton.isClickable = false
// The price provided to the API should include taxes and shipping.
// This price is not displayed to the user.
val garmentPrice = selectedGarment.getDouble("price")
val priceCents = Math.round(garmentPrice * PaymentsUtil.CENTS.toLong()) + SHIPPING_COST_CENTS
val paymentDataRequestJson = PaymentsUtil.getPaymentDataRequest(priceCents)
if (paymentDataRequestJson == null) {
Log.e("RequestPayment", "Can't fetch payment data request")
return
}
val request = PaymentDataRequest.fromJson(paymentDataRequestJson.toString())
// Since loadPaymentData may show the UI asking the user to select a payment method, we use
// AutoResolveHelper to wait for the user interacting with it. Once completed,
// onActivityResult will be called with the result.
if (request != null) {
AutoResolveHelper.resolveTask(
paymentsClient.loadPaymentData(request), this, LOAD_PAYMENT_DATA_REQUEST_CODE)
}
}
/**
* Handle a resolved activity from the Google Pay payment sheet.
*
* #param requestCode Request code originally supplied to AutoResolveHelper in requestPayment().
* #param resultCode Result code returned by the Google Pay API.
* #param data Intent from the Google Pay API containing payment or error data.
* #see [Getting a result
* from an Activity](https://developer.android.com/training/basics/intents/result)
*/
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
// Value passed in AutoResolveHelper
LOAD_PAYMENT_DATA_REQUEST_CODE -> {
when (resultCode) {
RESULT_OK ->
data?.let { intent ->
PaymentData.getFromIntent(intent)?.let(::handlePaymentSuccess)
}
RESULT_CANCELED -> {
// The user cancelled the payment attempt
}
AutoResolveHelper.RESULT_ERROR -> {
AutoResolveHelper.getStatusFromIntent(data)?.let {
handleError(it.statusCode)
}
}
}
// Re-enables the Google Pay payment button.
googlePayButton.isClickable = true
}
}
}
I recently came into this exact same issue. I didn't want to add the code to the activity and make an ugly, unreadable code so I ended up doing this:
In your composable, add this code to the click modifier or whatever:
val task = paymentsClient.loadPaymentData(request)
task.addOnCompleteListener { completedTask ->
if (completedTask.isSuccessful) {
completedTask.result.let{
//Do whatever you want
}
} else {
when (val exception = completedTask.exception) {
is ResolvableApiException -> {
resolvePaymentForResult.launch(
IntentSenderRequest.Builder(exception.resolution).build()
)
}
is ApiException -> {
Log.e("Google Pay API error", "Error code: ${exception.statusCode}, Message: ${exception.message}")
}
else -> {
Log.e("Unexpected non API exception")
}
}
}
}
With resolvePaymentForResult being:
val resolvePaymentForResult = rememberLauncherForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) {
result: ActivityResult ->
when (result.resultCode) {
RESULT_OK ->
result.data?.let { intent ->
PaymentData.getFromIntent(intent)?.let{
//Do whatever you want
}
}
RESULT_CANCELED -> {
// The user cancelled the payment attempt
}
}
}
You can always move paymentsClient.loadPaymentData(request) to your ViewModel if that's your architecture too!
Hope that will clean up your code a little bit more :)
Related
I have created the following extension function :
fun <T> Flow<T>.handleErrors(showError: Boolean = false, retry: Boolean = false,
navigateBack: Boolean = true): Flow<T> =
catch { throwable ->
var message: String? = null
if (showError) {
when (throwable) {
is HttpException -> {
postEvent(EventType(retry))
}
}
}
The extension function then posts the throwable type to a Base Activity and based on the event type posted a relevant dialog is displayed.
If the event is a retry, I would like to retry the failed flow.
For example if the HTTP exception is a 400, and I would like to retry the failed call when retry is selected on the dialog.
Is it possible to add callback to a Kotlin Flow, that has failed and can be called, from a different activity or fragment?
I don't think you want to retry in a separate block, you can organize your code like this
fun presentDialog(onClick: (Boolean) -> Unit) {
// some code to compile you dialog / install callbacks / show it
onClick(true) // user-click-retry
}
suspend fun main() {
val source = flow {
while (true) {
emit(
if (Math.random() > 0.5) Result.success(100) else Result.failure(IllegalArgumentException("woo"))
)
}
}
source.collect { result ->
suspendCancellableCoroutine<Unit> { cont ->
result.onSuccess {
println("we are done")
}.onFailure {
presentDialog { choice ->
if (choice) {
cont.resumeWith(Result.success(Unit))
} else {
println("we are done")
}
}
}
}
}
}
now some explanations
as you already know, flow is cold,if you don't collect it, it will never produce, as a result, if your collect block does not return, the remaining thunk in flow builder after emit will not get executed,
you suspend the execution of flow builder by calling suspendCoroutine in collect block, if an HTTP error occurs, you show your dialog, and resume the execution according to user response, if no error happens or users just don't click retry, leave everything alone. the sample code above is somehow misleading, for a general case, you don't supply a retry mechanism when everything goes fine, so the while true block could change to
flow {
do {
val response = response()
emit(response)
} while(response.isFailure)
}.collect {
it.onSuccess { println("of-coz-we-are-done") }.onFailure {
// suspend execution and show dialog
// resume execution when user click retry
}
}
which may comfort you that the flow has an end, but actually it is basically the same
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 decrement version build and name , so version build and version name are lower than the version on play store, i implement inapp version update of google , everything work fine, the download has complete so i add button to tell user restart application, and when he click it the install page is showen and application restart but when application start it's the same version like before , so the installation doesn't work :(
private fun showInAppUpdate() {
mAppUpdateManager = AppUpdateManagerFactory.create(activity);
mAppUpdateManager.registerListener(installStateUpdatedListener);
// Creates instance of the manager.
appUpdateManager = AppUpdateManagerFactory.create(activity)
// Returns an intent object that you use to check for an update.
val appUpdateInfoTask = appUpdateManager.appUpdateInfo
// Checks that the platform will allow the specified type of update.
appUpdateInfoTask.addOnSuccessListener { appUpdateInfo ->
val a = appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
val b = appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
// For a flexible update, use AppUpdateType.IMMEDIATE
&& appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)
) {
// To avoid crash when startUpdateFlowForResult is called two times (by accident)
try {
appUpdateManager.startUpdateFlowForResult(
// Pass the intent that is returned by 'getAppUpdateInfo()'.
appUpdateInfo,
// Or 'AppUpdateType.FLEXIBLE' for flexible updates.
AppUpdateType.FLEXIBLE,
// The current activity making the update request.
activity,
// Include a request code to later monitor this update request.
INAPP_UPDATE_REQUEST_CODE)
}
catch (e : Exception){
e.printStackTrace()
}
}
else {
Log.e("", "checkForAppUpdateAvailability: something else");
}
}
}
val installStateUpdatedListener = object : InstallStateUpdatedListener {
override fun onStateUpdate(state: InstallState) {
if (state.installStatus() == InstallStatus.DOWNLOADED) {
popupSnackbarForCompleteUpdate()
} else if (state.installStatus() == InstallStatus.INSTALLED) {
mAppUpdateManager?.unregisterListener(this)
} else {
// Download or install in progress
}
}
}
/* Displays the snackbar notification and call to action. */
fun popupSnackbarForCompleteUpdate() {
val snackBar = Snackbar.make(
activity!!.findViewById(R.id.home_root),
HtmlCompat.fromHtml(this.resources.getString(R.string.inapp_downlaod_complete),
HtmlCompat.FROM_HTML_MODE_LEGACY),
Snackbar.LENGTH_INDEFINITE
)
snackBar.setTextColor(Color.WHITE)
snackBar
.apply {
setAction(activity!!.resources.getString(R.string.inapp_restart)) {
appUpdateManager.completeUpdate() }
setActionTextColor(Color.GREEN)
show()
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == INAPP_UPDATE_REQUEST_CODE) {
if (resultCode != AppCompatActivity.RESULT_OK) {
mAppUpdateManager?.unregisterListener(installStateUpdatedListener)
}
}
}
why the application does not update with the version on play store ?
Thank you for your help
Mostly what is happening is that you are launching your first app from your code, then trying to update that app from the store. Ultimately, those are going to be 2 different apps (since the one from your code is going to be a debug version and unsigned); therefore, you won't see the update. If you want to test this, you would need to sign the app and install it on your phone rather than just launch it from the code.
I'm always getting incomplete at onCompletePayment and I'm also checked stripe sample app but it's also not working for me. I have check lot but I unable rectify the issue.
So what's would be error on my side?
Source :
PaymentConfiguration.init(BuildConfig.STRIPE_PUBLISHABLE_KEY)
/* Initialized customer*/
private fun setupPaymentSession() {
mPaymentSession = PaymentSession(mvpView?.baseActivity!!)
val paymentSessionInitialized = mPaymentSession!!.init(object : PaymentSession.PaymentSessionListener {
override fun onCommunicatingStateChanged(isCommunicating: Boolean) {
if (isCommunicating) {
} else {
}
}
override fun onError(errorCode: Int, errorMessage: String?) {
}
override fun onPaymentSessionDataChanged(data: PaymentSessionData) {
mPaymentSessionData = data
checkForCustomerUpdates()
}
}, PaymentSessionConfig.Builder()
/* .setPrepopulatedShippingInfo(getExampleShippingInfo())
.setHiddenShippingInfoFields(ShippingInfoWidget.PHONE_FIELD, ShippingInfoWidget.CITY_FIELD)*/
.setShippingInfoRequired(false)
.build())
if (paymentSessionInitialized) {
mPaymentSession?.setCartTotal(20L)
}
}
override fun handlePaymentData(requestCode: Int, resultCode: Int, data: Intent?) {
if (data != null) {
mPaymentSession?.handlePaymentData(requestCode, resultCode, data)
mPaymentSession?.completePayment(PaymentCompletionProvider { paymentData, listener ->
Toast.makeText(mvpView?.baseActivity!!, "success" + paymentData.paymentResult, Toast.LENGTH_SHORT).show()
listener.onPaymentResult(paymentData.paymentResult)
})
}
}
Not familiar with Kotlin but in the code snippet you provided, I'd suggest not to override handlePaymentData.
Instead call mPaymentSession.handlePaymentData in the onActivityResult of your host Activity like it is suggested in the doc here or as shown in the example here so that any updates to the PaymentSessionData is reported to your PaymentSessionListener that you attached when initializing PaymentSession (i.e with mPaymentSession!!.init).
Also generally, depending on your app Checkout flow, you would want to call mPaymentSession.completePayment(...) as a result of your user clicking for example on a "Pay" button.
You would pass to the completePayment(...) call a PaymentCompletionProvider which would:
send an HTTP request to your backend so that you can create a charge using Stripe's API
mark the result of the payment using listener.onPaymentResult(...) passing PaymentResultListener.SUCCESS in the case where the payment was for example successful.
I don't think that the example app has an example of this but in Java you could for example have a click listener on your "Pay" button setup like below:
Button payButton = findViewById(R.id.pay_bttn);
payButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (mPaymentSessionData.isPaymentReadyToCharge() && mPaymentSessionData.getPaymentResult() == PaymentResultListener.SUCCESS) {
// Use the data to complete your charge - see below.
mPaymentSession.completePayment(new PaymentCompletionProvider() {
#Override
public void completePayment(#NonNull PaymentSessionData data, #NonNull PaymentResultListener listener) {
Log.v(TAG, "Completing payment with data: " + data.toString());
// This is where you want to call your backend...
// In this case mark the payment as Successful
listener.onPaymentResult(PaymentResultListener.SUCCESS);
}
});
}
}
});
I hope this helps.
Issue: I need to refresh the Google token used for signing into my server. Most of the time this works well, but sometimes the call to Google to get a fresh token (with a TTL of ~1hr) fails for a variety of reasons.
Desired solution: some means of retrying the call to Google that will actually work.
I have code like the following in my app:
private val googleSignInClient: GoogleSignInClient by lazy {
// This takes a measurable amount of time to compute, so do it lazily
val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(WEB_CLIENT_ID) // need this to get user ID token later
.requestEmail()
.build()
GoogleSignIn.getClient(appContext, gso)
}
override fun getToken() = getRefreshedGoogleInfo()?.googleToken()
/**
* Here we "silently sign in" to get a refreshed Google ID Token.
*
* This method might block, so do not call it from the main thread.
*/
private fun getRefreshedGoogleInfo(): GoogleUserInfo? {
val task = googleSignInClient.silentSignIn()
// If the task is already complete, return the result immediately
if (task.isComplete) {
val info = task.result.toGoogleUserInfo()
Logger.v(TAG, "silentSignIn result from already-completed task = %s", info.toString())
return info
}
// If the task is not complete, await up to 5s and return result, or null
return try {
val info = task.await().toGoogleUserInfo()
Logger.v(TAG, "silentSignIn result from await task = %s", info.toString())
info
} catch (e: Exception) {
Logger.e(TAG, e, "silentSignIn result from await task = null\nerror = ${e.localizedMessage}")
null
}
}
private fun Task<GoogleSignInAccount>.await() = Tasks.await(this, 5, TimeUnit.SECONDS)
Sometimes, the call task.await() will fail because it timed out. In such a case, what is the best strategy to try again? I have tried a naive strategy of just trying again immediately up to some arbitrary numerical limit, but I have observed that if it fails the first time, it always fails on subsequent attempts. The Google docs aren't very helpful with respect to this scenario.
Instead of waiting the task up to 5 seconds, why not try again with requests and intents ? Let me show you in your code with edits.
private fun getRefreshedGoogleInfo(): GoogleUserInfo? {
val task = googleSignInClient.silentSignIn()
// If the task is already complete, return the result immediately
if (task.isComplete) {
val info = task.result.toGoogleUserInfo()
Logger.v(TAG, "silentSignIn result from already-completed task = %s", info.toString())
return info
}
else{ //Else is not necessary, but it will somehow increase readability.
// There's no immediate result ready, displays some progress indicator and waits for the async callback.
task.addOnCompleteListener(this){ signInTask ->
//We repeat the same task control again, but this time it is async.
if(signInTask.isComplete){
val info = task.result.toGoogleUserInfo() //Redone the same things in first task.isComplete scope.
Logger.v(TAG, "silentSignIn result from already-completed task = %s", info.toString())
return info
}
else{ //Again, not necessary but still.
//This is where we are gonna try again.
signInToGoogle() //Go below to see the trick.
}
}
}
}
With the code above alone, you do not have to wait for 5 seconds, but it will not try again. To try again with more persistant way ( I assume you use these functions on an activity ) :
private fun signInToGoogle(){
val signInIntent = googleSignInClient!!.signInIntent
startActivityForResult(signInIntent, RC_SIGN_IN) //You might consider checking for internet connection before calling this
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if(requestCode == RC_SIGN_IN){
val task = GoogleSignIn.getSignedInAccountFromIntent(data)
/* your code with task handle goes here */
//task.result carries the same object as above. But since this is a listener, it can not be returned with a value. So it is up to you how to handle again. I handle this with a class-wide variable which contains GoogleUserInfo?
}
}
RC_SIGN_IN is just a number for request. You could add it to your companion objects like this or use it as it is :
companion object {
private const val RC_SIGN_IN = 9001
}
Note : You could use intents for the first try too. Or if intents are too much hassle for you, you could Make function sleep for 5 seconds and call the function again.
But i strongly recommend you to use intent.