Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. Mobile and Embedded
  4. Qt 6.5.3 - Implementing Google One Tap Sign On for Android
Qt 6.11 is out! See what's new in the release blog

Qt 6.5.3 - Implementing Google One Tap Sign On for Android

Scheduled Pinned Locked Moved Unsolved Mobile and Embedded
2 Posts 1 Posters 1.1k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • G Offline
    G Offline
    gween
    wrote on last edited by
    #1

    I've been trying to implement Google's one tap sign on for android. I've been trying to follow the steps listed in their docs https://developers.google.com/identity/one-tap/android/get-saved-credentials#java, but I don't really know anything about java so I've been flying by the seat of my pants.

    I have my java file GoogleAuth.java (shown below), and I try to call DisplayOneTapSignIn in my cpp code:

    // GoogleAuth.cpp
    if (QJniObject::isClassAvailable("com/test/google/GoogleAuth")) {
            qDebug() << "Found GoogleAuth Class";
            QJniObject googleAuth = QJniObject("com/test/google/GoogleAuth");
            googleAuth.callObjectMethod<void>("DisplayOneTapSignIn");
        } else {
            qDebug() << "Could not find GoogleAuth Class";
        }
    

    This doesn't work, though and fails with:

    Can't create handler inside thread that has not called Looper.prepare()

    I don't really know how to proceed from here. From what I've read I need to run my java class in a UI thread. I tried a few things in the java class that I found on threads, but none of them work.

    This lead me to find this from Qt 5.15 https://doc.qt.io/qt-5/qtandroidextras-customactivity-example.html

    Do I need to proceed by doing something along the lines in the Qt 5.15 example link above since my java class extends AppCompatActivity? Or should I be doing something via java like creating a wrapper class that runs my GoogleAuth class in a ui thread? If the latter, any suggestions on how to do that?

    // GoogleAuth.java
    package com.test.google;
    
    import android.util.Log;
    import com.google.android.gms.auth.api.identity.SignInClient;
    import com.google.android.gms.auth.api.identity.BeginSignInRequest;
    import com.google.android.gms.auth.api.identity.BeginSignInRequest.Builder;
    import com.google.android.gms.auth.api.identity.BeginSignInRequest.GoogleIdTokenRequestOptions;
    import com.google.android.gms.auth.api.identity.BeginSignInResult;
    import com.google.android.gms.auth.api.identity.Identity;
    import android.os.PersistableBundle;
    import android.os.Bundle;
    import android.os.Looper;
    import androidx.annotation.Nullable;
    import androidx.annotation.NonNull;
    import androidx.appcompat.app.AppCompatActivity;
    import android.content.IntentSender;
    import android.content.IntentSender.SendIntentException;
    import com.google.android.gms.tasks.OnSuccessListener;
    import com.google.android.gms.tasks.OnFailureListener;
    
    public class GoogleAuth extends AppCompatActivity {
        // ...
    
        private static final String TAG = "GoogleAuth";
    
        private static final int REQ_ONE_TAP = 2;
        private boolean showOneTapUI = true;
      
        private SignInClient oneTapClient;
        private BeginSignInRequest signInRequest;
      
        @Override
        public void onCreate(@Nullable Bundle savedInstanceState,
                             @Nullable PersistableBundle persistentState) {
            super.onCreate(savedInstanceState, persistentState);
    
            Log.d(TAG, "onCreate start...");
      
            // This is where your UI code goes.
            oneTapClient = Identity.getSignInClient(this);
            signInRequest = BeginSignInRequest.builder()
            // .setPasswordRequestOptions(PasswordRequestOptions.builder()
            //         .setSupported(true)
            //         .build())
            .setGoogleIdTokenRequestOptions(GoogleIdTokenRequestOptions.builder()
                    .setSupported(true)
                    // Your server's client ID, not your Android client ID.
                    .setServerClientId("my-client-id")
                    // Only show accounts previously used to sign in.
                    .setFilterByAuthorizedAccounts(true)
                    .build())
            // Automatically sign in when exactly one credential is retrieved.
            .setAutoSelectEnabled(true)
            .build();
    
            Log.d(TAG, "onCreate end...");
    
        }
    
        public void DisplayOneTapSignIn() {
            Log.d(TAG, "DisplayOneTapSignIn start...");
    
            oneTapClient.beginSignIn(signInRequest)
                    .addOnSuccessListener(this, new OnSuccessListener<BeginSignInResult>() {
                    @Override
                    public void onSuccess(BeginSignInResult result) {
                            try {
                            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());
                            }
                    }
                    })
                    .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());
                    }
                    });
            Log.d(TAG, "DisplayOneTapSignIn end...");
        }
        // ...
      }
    
    
    1 Reply Last reply
    0
    • G Offline
      G Offline
      gween
      wrote on last edited by gween
      #2

      A little progress:

      Running setup below works without error, but then when running showOneTapSignOn I get the following error:

      E AndroidRuntime: FATAL EXCEPTION: main
      E AndroidRuntime: Process: com.auth.google, PID: 16379
      E AndroidRuntime: java.lang.NullPointerException: Attempt to invoke virtual method 'android.app.ActivityThread$ApplicationThread android.app.ActivityThread.getApplicationThread()' on a null object reference
      E AndroidRuntime: 	at android.app.Activity.startIntentSenderForResultInner(Activity.java:5970)
      E AndroidRuntime: 	at android.app.Activity.startIntentSenderForResult(Activity.java:5941)
      E AndroidRuntime: 	at androidx.activity.ComponentActivity.startIntentSenderForResult(ComponentActivity.java:772)
      E AndroidRuntime: 	at androidx.core.app.ActivityCompat$Api16Impl.startIntentSenderForResult(ActivityCompat.java:816)
      E AndroidRuntime: 	at androidx.core.app.ActivityCompat.startIntentSenderForResult(ActivityCompat.java:283)
      E AndroidRuntime: 	at androidx.activity.ComponentActivity$2.onLaunch(ComponentActivity.java:228)
      E AndroidRuntime: 	at androidx.activity.result.ActivityResultRegistry$2.launch(ActivityResultRegistry.java:175)
      E AndroidRuntime: 	at androidx.activity.result.ActivityResultLauncher.launch(ActivityResultLauncher.java:47)
      E AndroidRuntime: 	at com.auth.google.GoogleAuth$3.onSuccess(GoogleAuth.java:196)
      E AndroidRuntime: 	at com.auth.google.GoogleAuth$3.onSuccess(GoogleAuth.java:191)
      E AndroidRuntime: 	at com.google.android.gms.tasks.zzm.run(com.google.android.gms:play-services-tasks@@18.0.2:1)
      E AndroidRuntime: 	at android.os.Handler.handleCallback(Handler.java:958)
      E AndroidRuntime: 	at android.os.Handler.dispatchMessage(Handler.java:99)
      E AndroidRuntime: 	at android.os.Looper.loopOnce(Looper.java:205)
      E AndroidRuntime: 	at android.os.Looper.loop(Looper.java:294)
      E AndroidRuntime: 	at android.app.ActivityThread.main(ActivityThread.java:8177)
      E AndroidRuntime: 	at java.lang.reflect.Method.invoke(Native Method)
      E AndroidRuntime: 	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
      E AndroidRuntime: 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)
      

      C++ side:

      GoogleAuth::GoogleAuth(QObject *parent)
          : QObject{parent}
      {
      QNativeInterface::QAndroidApplication::runOnAndroidMainThread([=]() {
              if (QJniObject::isClassAvailable("com/auth/google/GoogleAuth")) {
                  qDebug() << "Found GoogleAuth Class";
                  m_gauth = QJniObject("com/auth/google/GoogleAuth");
                  if (!m_gauth.isValid()) {
                      qDebug() << "m_gauth object not valid";
                  } else {
                      qDebug() << "m_gauth Valid";
                      m_gauth.callMethod<void>("setup", QNativeInterface::QAndroidApplication::context());
                  }
              } else {
                  qDebug() << "Could not find GoogleAuth Class";
              }
          }).waitForFinished();
      }
      
      ...
      
      void GoogleAuth::Auth()
      {
          QNativeInterface::QAndroidApplication::runOnAndroidMainThread([=]() {
              m_gauth.callMethod<void>("showOneTapSignOn", QNativeInterface::QAndroidApplication::context());
          }).waitForFinished();
      }
      

      Java side:

      Note that I've altered the function below based on this video which says the Google docs uses a deprecated function: https://www.youtube.com/watch?v=Zz3412C4BSA

      public class GoogleAuth extends AppCompatActivity {
      
          private SignInClient oneTapClient;
          private BeginSignInRequest signUpRequest;
          private ActivityResultLauncher<IntentSenderRequest> activityResultLauncher;
          private static final String TAG = "GoogleAuth";
          private static final int REQ_ONE_TAP = 2;
      
          public GoogleAuth() {
              Log.i(TAG, "GoogleAuth ctor running");
          }
      
          public void setup(Context qtContext) {
              if (qtContext == null) {
                  Log.i(TAG, "qtContext == null");
              }
              oneTapClient = Identity.getSignInClient(qtContext);
              Log.i(TAG, "oneTapClient created");
              signUpRequest = BeginSignInRequest.builder()
                      .setGoogleIdTokenRequestOptions(GoogleIdTokenRequestOptions.builder()
                              .setSupported(true)
                              // Your server's client ID, not your Android client ID.
                              .setServerClientId("<server client id>")
                              .setFilterByAuthorizedAccounts(false)
                              .build())
                      .build();
      
              activityResultLauncher =
                  registerForActivityResult(new ActivityResultContracts.StartIntentSenderForResult(),
                  new ActivityResultCallback<ActivityResult>() {
                      @Override
                      public void onActivityResult(ActivityResult result) {
                          Log.i(TAG, "onActivityResult start");
                          if (result.getResultCode() == Activity.RESULT_OK) {
                              Log.i(TAG, "result.getResultCode() == Activity.RESULT_OK");
                              try {
                                  SignInCredential credential = oneTapClient.getSignInCredentialFromIntent(result.getData());
                                  String idToken = credential.getGoogleIdToken();
                                  if (idToken != null) {
                                      Log.i(TAG, "Got ID token");
                                      // authenticate with backend server here!
                                      String email = credential.getId();
                                      Log.i(TAG, "Email = " + email);
                                  }
                              } catch (ApiException e) {
                                  Log.i(TAG, "ApiException!!");
                                  e.printStackTrace();
                              }
                          }
                      }
                  });
          }
      
          public void showOneTapSignOn(Context qtContext) {
              Log.i(TAG, "showOneTapSignOnSTART!");
              oneTapClient.beginSignIn(signUpRequest)
                  .addOnSuccessListener((Activity)qtContext, new OnSuccessListener<BeginSignInResult>() {
                      @Override
                      public void onSuccess(BeginSignInResult result) {
                          IntentSenderRequest intentSenderRequest =
                                  new IntentSenderRequest.Builder(result.getPendingIntent().getIntentSender()).build();
                          activityResultLauncher.launch(intentSenderRequest);
                      }
                  })
                  .addOnFailureListener((Activity)qtContext, new OnFailureListener() {
                      @Override
                      public void onFailure(@NonNull Exception e) {
                          // No Google Accounts found. Just continue presenting the signed-out UI.
                          Log.d(TAG, e.getLocalizedMessage());
                      }
                  });
              Log.i(TAG, "showOneTapSignOn END!");
          }
        }
      
      1 Reply Last reply
      0

      • Login

      • Login or register to search.
      • First post
        Last post
      0
      • Categories
      • Recent
      • Tags
      • Popular
      • Users
      • Groups
      • Search
      • Get Qt Extensions
      • Unsolved