Search code examples
c#xamarin.formsfirebase-authenticationxamarin.androidgoogle-signin

How to use the new RegisterForActivityResult in Xamarin Android, i.e. for GoogleSignIn


I read that StartActivityForResult is deprecated and it is anyway not nice to have the callback in MainActivity.OnActivityResult() when you start the login process elsewhere.

Unfortunately I couldn't get any of the examples translated, seems Xamarin is missing essential things?


Solution

  • The missing part was that there is no implementation of IActivityResultCallback anywhere, so I created my own. Hope this will be included in Xamarin some time.

    public class ActivityResultCallback : Java.Lang.Object, IActivityResultCallback
    {
        readonly Action<ActivityResult> _callback;
        public ActivityResultCallback(Action<ActivityResult> callback) => _callback = callback;
        public ActivityResultCallback(TaskCompletionSource<ActivityResult> tcs) => _callback = tcs.SetResult;
        public void OnActivityResult(Java.Lang.Object p0) => _callback((ActivityResult)p0);
    }
    

    Using the TaskCompletionSource approach you can even await the callback. Here is how I used it for GoogleSignIn:

    readonly Func<Task<GoogleSignInResult>> _googleSignInAsync = PrepareGoogleSignIn(
        MainActivity.SharedInstance, "123-321.apps.googleusercontent.com"); //TODO: read from google-services.json
    
    static Func<Task<GoogleSignInResult>> PrepareGoogleSignIn(ComponentActivity activity, string serverClientId)
    {
        var googleSignInOptions = new GoogleSignInOptions.Builder(GoogleSignInOptions.DefaultSignIn)
            .RequestIdToken(serverClientId)
            .RequestEmail()
            .Build();
    
        var googleSignInClient = GoogleSignIn.GetClient(activity, googleSignInOptions);
    
        TaskCompletionSource<GoogleSignInResult> taskCompletionSource = null;
    
        var activityResultLauncher = activity.RegisterForActivityResult(
            new ActivityResultContracts.StartActivityForResult(),
            new ActivityResultCallback(activityResult => taskCompletionSource?.SetResult(
                Auth.GoogleSignInApi.GetSignInResultFromIntent(activityResult.Data))));
    
        return () => {
            taskCompletionSource = new TaskCompletionSource<GoogleSignInResult>();
            activityResultLauncher.Launch(googleSignInClient.SignInIntent);
            return taskCompletionSource.Task;
        };
    }
    

    I have this in the Android implementation of my authentication service. You can also prepare it in MainActivity.OnCreate() with parameter this. Make sure it is called before the app goes to running state.

    Later you can just await _googleSignInAsync(). Neat!


    Some bonus code how to use it and forward to Firebase auth:

    using Android.Gms.Auth.Api;
    using Android.Gms.Auth.Api.SignIn;
    using Android.Gms.Extensions;
    using AndroidX.Activity;
    using AndroidX.Activity.Result;
    using AndroidX.Activity.Result.Contract;
    using Firebase.Auth;
    
    var signInResult = await _googleSignInAsync();
    Console.WriteLine($"{signInResult.Status} {signInResult.SignInAccount?.DisplayName}");
    if (!signInResult.IsSuccess || signInResult.SignInAccount == null)
        throw new GoogleSignInCanceledException();
    
    var credential = GoogleAuthProvider.GetCredential(signInResult.SignInAccount.IdToken, null);
    var authResult = await FirebaseAuth.Instance.SignInWithCredentialAsync(credential);
    AuthToken = (await authResult.User.GetIdToken(false).AsAsync<GetTokenResult>()).Token;
    

    Any improvements? I'm happy to hear from you. Or are you looking for the iOS implementation?