androidgoogle-apiandroid-signinggoogle-one-tap

How to use Google Activity Result API in One Tap sign-in


In one of my Android applications I am trying to include One Tap sign-in authentication by following the example https://developers.google.com/identity/one-tap/android/get-saved-credentials#disable-one-tap. However, here https://developer.android.com/reference/androidx/activity/ComponentActivity#startActivityForResult(android.content.Intent,int) it says that the function startActivityForResult is deprecated and to use instead the function registerForActivityResult passing in a StartActivityForResult object for the ActivityResultContract. With the help of other examples, I could write the following code

 public class MainActivity extends AppCompatActivity {


    private SignInClient oneTapClient;
    private BeginSignInRequest signInRequest;

    private ActivityResultLauncher<Intent> loginResultHandler = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
        // handle intent result here
        if (result.getResultCode() == RESULT_OK) {
            SignInCredential credential = null;
            try {
                credential = oneTapClient.getSignInCredentialFromIntent(result.getData());
                String idToken = credential.getGoogleIdToken();
                String username = credential.getId();
                String password = credential.getPassword();
                if (idToken != null) {
                    // Got an ID token from Google. Use it to authenticate
                    // with your backend.
                    Log.d(TAG, "Got ID token.");
                } else if (password != null) {
                    // Got a saved username and password. Use them to authenticate
                    // with your backend.
                    Log.d(TAG, "Got password.");
                }
            } catch (ApiException e) {
                e.printStackTrace();
            }
        }
        else {
            //...
        }
    });

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //...

        oneTapClient.beginSignIn(signInRequest)
                .addOnSuccessListener(this, new OnSuccessListener<BeginSignInResult>() {
                    @Override
                    public void onSuccess(BeginSignInResult result) {
                       /*try {
                           android.app.Activity.startIntentSenderForResult(result.getPendingIntent().getIntentSender(), REQ_ONE_TAP,
                                    null, 0, 0, 0);
                        } catch (IntentSender.SendIntentException e) {
                            Log.e(TAG, "Couldn't start One Tap UI: " + e.getLocalizedMessage());
                        }*/
                        loginResultHandler.launch(result.getPendingIntent().getIntentSender());
                    }
                })
                .addOnFailureListener(this, new OnFailureListener() {
                    @Override
                    public void onFailure(@NonNull Exception e) {
                        // No saved credentials found. Launch the One Tap sign-up flow, or
                        // do nothing and continue presenting the signed-out UI.
                        Log.d(TAG, e.getLocalizedMessage());
                    }
                });
    }
}

only I can't figure out how to convert the instruction loginResultHandler.launch(result.getPendingIntent().getIntentSender()) according to the Google Activity Result API (getIntentSender() returns an IntentSender but loginResultHandler.launch() requires an Intent). Would anyone be able to help me or link me working examples? Thank you.

EDIT: Finally, I rewrote the code in the following way (I have tested it up to the account selection screen and it works):

public class MainActivity extends AppCompatActivity {

   private static final String TAG = "MainActivity";
    private SignInClient oneTapClient;
    private BeginSignInRequest signInRequest;
    private boolean showOneTapUI = true;

    private ActivityResultLauncher<IntentSenderRequest> loginResultHandler = registerForActivityResult(new ActivityResultContracts.StartIntentSenderForResult(), result -> {
        // handle intent result here
        if (result.getResultCode() == RESULT_OK) Log.d(TAG, "RESULT_OK.");
        if (result.getResultCode() == RESULT_CANCELED) Log.d(TAG, "RESULT_CANCELED.");
        if (result.getResultCode() == RESULT_FIRST_USER) Log.d(TAG, "RESULT_FIRST_USER.");
            try {
                SignInCredential credential = oneTapClient.getSignInCredentialFromIntent(result.getData());
                String idToken = credential.getGoogleIdToken();
                String username = credential.getId();
                String password = credential.getPassword();
                if (idToken !=  null) {
                    // Got an ID token from Google. Use it to authenticate
                    // with your backend.
                    Log.d(TAG, "Got ID token.");
                } else if (password != null) {
                    // Got a saved username and password. Use them to authenticate
                    // with your backend.
                    Log.d(TAG, "Got password.");
                }
            } catch (ApiException e) {
                switch (e.getStatusCode()) {
                    case CommonStatusCodes.CANCELED:
                        Log.d(TAG, "One-tap dialog was closed.");
                        // Don't re-prompt the user.
                        showOneTapUI = false;
                        break;
                    case CommonStatusCodes.NETWORK_ERROR:
                        Log.d(TAG, "One-tap encountered a network error.");
                        // Try again or just ignore.
                        break;
                    default:
                        Log.d(TAG, "Couldn't get credential from result."
                                + e.getLocalizedMessage());
                        break;
                }
            }
    });

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //...

        oneTapClient.beginSignIn(signInRequest)
                .addOnSuccessListener(this, new OnSuccessListener<BeginSignInResult>() {
                    @Override
                    public void onSuccess(BeginSignInResult result) {
                        try {
                            loginResultHandler.launch(new IntentSenderRequest.Builder(result.getPendingIntent().getIntentSender()).build());
                        } catch(android.content.ActivityNotFoundException e){
                            e.printStackTrace();
                            Log.e(TAG, "Couldn't start One Tap UI: " + e.getLocalizedMessage());
                        }
                    }
                })
                .addOnFailureListener(this, new OnFailureListener() {
                    @Override
                    public void onFailure(@NonNull Exception e) {
                        // No saved credentials found. Launch the One Tap sign-up flow, or
                        // do nothing and continue presenting the signed-out UI.
                        Log.d(TAG, e.getLocalizedMessage());
                    }
                });
    }
}

Solution

  • You can use the ActivityResultContracts.StartIntentSenderForResult class instead of the ActivityResultContracts.StartActivityForResult

    And therefore you can refactor your code to something like this,

    First define what will be done when the result is given to you using the ActivityResultLauncher<IntentSenderRequest> instance rather than the ActivityResultLauncher<Intent> instance

    private ActivityResultLauncher<IntentSenderRequest> loginResultHandler = registerForActivityResult(new ActivityResultContracts.StartIntentSenderForResult(), result -> {
            // handle intent result here
            if (result.getResultCode() == RESULT_OK) {
                SignInCredential credential = null;
                try {
                    credential = oneTapClient.getSignInCredentialFromIntent(result.getData());
                    String idToken = credential.getGoogleIdToken();
                    String username = credential.getId();
                    String password = credential.getPassword();
                    if (idToken != null) {
                        // Got an ID token from Google. Use it to authenticate
                        // with your backend.
                        Log.d(TAG, "Got ID token.");
                    } else if (password != null) {
                        // Got a saved username and password. Use them to authenticate
                        // with your backend.
                        Log.d(TAG, "Got password.");
                    }
                } catch (ApiException e) {
                    e.printStackTrace();
                }
            }
            else {
                //...
            }
        });
    

    And now in your onCreate as you mentioned above in your code, you can use IntentSenderRequest.Builder class to turn your IntentSender instance into a IntentSenderRequest instance which will be accepted in the launch method

    oneTapClient.beginSignIn(signInRequest)
                    .addOnSuccessListener(this, new OnSuccessListener<BeginSignInResult>() {
                        @Override
                        public void onSuccess(BeginSignInResult result) {
                         
    
                            try{
                                loginResultHandler.launch(new IntentSenderRequest.Builder(result.getPendingIntent().getIntentSender()).build());
                               }catch(IntentSenderException e){
                                  e.printStackTrace()
                                }
                            
                        }
                    })
                    .addOnFailureListener(this, new OnFailureListener() {
                        @Override
                        public void onFailure(@NonNull Exception e) {
                            // No saved credentials found. Launch the One Tap sign-up flow, or
                            // do nothing and continue presenting the signed-out UI.
                            Log.d(TAG, e.getLocalizedMessage());
                        }
                    });
        }
    
    

    Make sure you wrap that in a try-catch construct and it should be it.

    Just came across this answer while googling, it might help you too