Firebase para aplicaciones Xamarin.Forms – Parte 1. FirebaseAuth para Android

Después del anterior artículo donde introducíamos el concepto de Firebase y explicábamos cual iba a ser nuestro cometido empezamos hoy con la implementación. Hoy nos centraremos en la autenticación para el proyecto de Android de nuestra aplicación Xamarin.Forms.

Inicio

Partiremos de un proyecto Xamarin.Forms diseñado con un patrón MVVM, una pantalla de login, una de registro y una final donde sólo accederemos una vez logado.

Puedes descargarte el proyecto inicial desde el siguiente enlace:https://github.com/jamontes79/xamarin-forms-firebase-sample/tree/02e843e9f06e467f454cd49b9cd0f5b00f14c995

Con esto ya tenemos la estructura de nuestro proyecto creada, lo siguiente que haremos será crear nuestro proyecto en Firebase, para ello accederemos a la consola de desarrollador de Firebase desde el siguiente enlace: https://firebase.google.com.

Configuración Firebase

Una vez dentro debemos crear nuestro proyecto, para ello usaremos el siguiente enlace desde la consola:

Nos saldrá la siguiente ventana en la que introduciremos los datos básicos de nuestro proyecto:

Una vez hecho, ya tenemos nuestro proyecto Firebase creado y veremos lo siguiente:

A partir de aquí configuraremos nuestro proyecto firebase Android, que al ser de Xamarin.Forms será un poquito diferente a como nos explica Firebase en sus tutorales.

Configurando Firebase para nuestro proyecto Android

Empezaremos pinchando sobre el enlace “Añade Firebase a tu aplicación de Android”, esto nos abre la siguiente ventana:

Estos datos son muy importantes:

  • El primero como sabréis es el identificador interno de nuestro proyecto Android, y debe ser único.
  • El segundo es opcional y es el nombre con el que encontrareis el proyecto dentro de la consola de Firebase
  • El tercer es muy importante, ya que es la clave del certificado con el que firmáis la aplicación, este será diferente si estáis en modo debug o ya estáis firmando la aplicación para su distribución, os recomiendo tener dos proyectos firebase para cada uno.
    • Esta firma se obtiene de la siguiente forma para el certificado en modo debug:
        • Windows
      keytool.exe -list -v -keystore "%LocalAppData%\Xamarin\Mono for Android\debug.keystore" -alias androiddebugkey -storepass android -keypass android
      
        • Mac
      keytool -list -v -keystore ~/.local/share/Xamarin/Mono\ for\ Android/debug.keystore -alias androiddebugkey -storepass android -keypass android
      
    • Para el modo release únicamente tendréis que sustituir la ruta y la contraseña por la de vuestro certificado.

El siguiente paso nos muestra la siguiente ventana, desde la que descargaremos el archivo enlazado y lo guardaremos ya que posteriormente lo usaremos en nuestro proyecto.

Le damos a continuar hasta terminar los tres paso y con esto ya tendremos configurada la consola de Firebase para nuestro proyecto Android.

El siguiente pasó será indicar como queremos que nuestra aplicación se autentique.

Configurando métodos de autenticación.

En el menú lateral de la consola de firebase tenemos un enlace llamado “Authentication“, si pinchamos ahí podremos ver los usuarios que están registrados en nuestra aplicación, así como configurar los diferentes accesos desde la pestaña “Método de inicio de sesión“.

Para nuestro ejemplo configuraremos la autenticación por medio de correo/contraseña, con lo que únicamente debemos habilitarlo. y el inicio de sesión con Google.

Es muy importante que al habilitar el inicio de sesión con Google nos apuntemos el dato “Id Cliente Web”, ya que posteriormente nos permitirá configurar nuestro proyecto Android.

Con esto ya tenemos terminada la parte de configuración en la consola de firebase empecemos con el código.

Configurando nuestro proyecto

Lo primero que haremos será instalar los paquetes Nugets necesarios, recuerda que de momento lo haremos en el proyecto Android.

Empezaremos por instalar los paquetes de Google Play Services, para ello en el proyecto Android usaremos la siguiente opción:

Una vez dentro nos da la opción además de elegir el paquete de Autenticación con Firebase, lo elegimos también:

Con esto ya tenemos los paquetes Nugets instalados.

Añadiendo Google-Services.json

Si recordamos al configurar la consola de Firebase, nos descargamos un fichero “google-services.json”, es el momento de añadirlo a la raíz nuestro proyecto Android, una vez añadido cambiaremos su acción de compilación por “GoogleServicesJson”, tal como se ve en la imagen. Si no tienes la opción cierra la solución y vuelve a abrirla, y ya te aparecerán las opciones.

Ya casi tenemos el proyecto configurado, sólo nos falta añadir el paquete Nuget de “Google Play Services Auth”, esto nos permitirá logarnos con la cuenta de Google:

Ahora ya si, tenemos configurado nuestro proyecto y empezaremos con la implementación.

Implementado el código

Al usar un proyecto PCL haremos usos de la inyección de dependencias, si quieres leer más sobre el tema puedes hacerlo en el siguiente enlace: https://javiersuarezruiz.wordpress.com/2015/06/30/xamarin-forms-utilizando-dependencyservice/

Inicializando Firebase

Para inicializar Firebase para nuestro proyecto Android debemos añadir unas lineas a nuestra clase MainActivity:

public static FirebaseApp app;
protected override void OnCreate(Bundle bundle)
{
    TabLayoutResource = Resource.Layout.Tabbar;
    ToolbarResource = Resource.Layout.Toolbar;

    base.OnCreate(bundle);
    InitFirebaseAuth();
    UserDialogs.Init(this);
    global::Xamarin.Forms.Forms.Init(this, bundle);

    LoadApplication(new App());
}

private void InitFirebaseAuth()
{
    var options = new FirebaseOptions.Builder()
    .SetApplicationId("1:2485447395:android:1bf7180db061f771")
    .SetApiKey("AIzaSyBdszK9ZCwbukS8Qb1iZ_LCXVq2os-KYJA")
    .Build();



    if (app == null)
        app = FirebaseApp.InitializeApp(this, options, "FirebaseSample");

}

¿De dónde salen esos números y códigos que hemos puesto?No preocuparos, salen de la consola de Firebase:

Definición del servicio

Lo primero que haremos será crear la definición del servicio en nuestro proyecto común que llamaremos IFirebaseAuthService.

using System;
using System.Threading.Tasks;

namespace firebasesample.Services.FirebaseAuth
{
public interface IFirebaseAuthService
{
String getAuthKey();
bool IsUserSigned();
Task<bool> SignUp(String email, String password);
Task<bool> SignIn(String email, String password);
void SignInWithGoogle();
Task<bool> SignInWithGoogle(String token);
Task<bool> Logout();
}
}

Implementación del servicio en Android

Lo primero que haremos será crear en nuestro proyecto Android un servicio que mediante inyección de dependencias implemente la interfaz IFirebaseAuthService recién creada:

using System;
using System.Threading.Tasks;
using firebasesample.Droid.Services.FirebaseAuth;
using firebasesample.Services.FirebaseAuth;
using Xamarin.Forms;


[assembly: Dependency(typeof(FirebaseAuthService))]
namespace firebasesample.Droid.Services.FirebaseAuth
{
    public class FirebaseAuthService : IFirebaseAuthService
    {
        public string getAuthKey()
        {
            throw new NotImplementedException();
        }

        public bool IsUserSigned()
        {
            throw new NotImplementedException();
        }

        public Task<bool> Logout()
        {
            throw new NotImplementedException();
        }

        public Task<bool> SignIn(string email, string password)
        {
            throw new NotImplementedException();
        }

        public void SignInWithGoogle()
        {
            throw new NotImplementedException();
        }

        public Task<bool> SignInWithGoogle(string token)
        {
            throw new NotImplementedException();
        }

        public Task<bool> SignUp(string email, string password)
        {
            throw new NotImplementedException();
        }
    }
}

Con esto ya tenemos el esqueleto de nuestro servicio, ahora iremos implementando cada una de las funciones, que como verás la mayoría son bastantes sencillas.

Comprobar si el usuario está logado: IsUserSigned

public bool IsUserSigned()
{
    var user = Firebase.Auth.FirebaseAuth.GetInstance(MainActivity.app).CurrentUser;
    var signedIn = user != null;
    return signedIn;
}

Logarnos mediante email y contraseña: SignIn(string email, string password)

public async Task<bool> SignIn(string email, string password)
{
    try
    {

        await Firebase.Auth.FirebaseAuth.GetInstance(MainActivity.app).SignInWithEmailAndPasswordAsync(email, password);
        return true;
    }
    catch (Exception ex)
    {
        return false;
    }
}

Registrarnos mediante email y contraseña: SignUp(string email, string password)

public async Tas<bool> SignUp(string email, string password)
{
    try
    {
        await Firebase.Auth.FirebaseAuth.GetInstance(MainActivity.app).CreateUserWithEmailAndPasswordAsync(email, password);
        return true;
    }
    catch (Exception ex)
    {
        return false;
    }
}

Salir de la aplicación: Logout

public async Task<bool> Logout()
{
    try
    {
            Firebase.Auth.FirebaseAuth.GetInstance(MainActivity.app).SignOut();
        return true;
    }
    catch (Exception ex)
    {
        return false;
    }
}

Con estas sencillas lineas de código ya tenemos implementada la autenticación básica, ahora empezaremos con la autenticación mediante cuenta de Google.

Autenticación con Google

Hasta hemos visto que ha sido muy sencillo integrar firebase, para esta parte si que tendremos que hacer alguna cosilla más.

Crearemos una nueva clase llamada GoogleLoginActiviy, que será la que se encargue de mostrar el diálogo con la cuenta de Google, seleccionarla, etc…

El código es el siguiente:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.Support.V7.App;
using Android.Gms.Common.Apis;
using Android.Gms.Common;
using System.Threading.Tasks;
using Android.Gms.Auth.Api.SignIn;
using Android.Gms.Auth.Api;
using Firebase.Auth;

namespace firebasesample.Droid.Activities
{
    [Activity(Label = "GoogleLogin", Theme = "@style/Theme.AppCompat.Light.DarkActionBar")]
    public class GoogleLoginActivity : AppCompatActivity, GoogleApiClient.IConnectionCallbacks, GoogleApiClient.IOnConnectionFailedListener
    {
        const string TAG = "GoogleLoginActivity";

        const int RC_SIGN_IN = 9001;

        const string KEY_IS_RESOLVING = "is_resolving";
        const string KEY_SHOULD_RESOLVE = "should_resolve";


        static GoogleApiClient mGoogleApiClient;

        bool mIsResolving = false;

        bool mShouldResolve = false;

       
        private static GoogleSignInAccount mAuth;
        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);

            GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DefaultSignIn)
              .RequestIdToken("2485447395-g5sdqpgfdjklgo2f1ir84s0cedsdqgv1.apps.googleusercontent.com")
                .Build();

            mGoogleApiClient = new GoogleApiClient.Builder(this)
                .AddConnectionCallbacks(this)
                .AddOnConnectionFailedListener(this)
                .AddApi(Auth.GOOGLE_SIGN_IN_API, gso)
                .Build();

            Intent signInIntent = Auth.GoogleSignInApi.GetSignInIntent(mGoogleApiClient);
            StartActivityForResult(signInIntent, RC_SIGN_IN);
        }

        private void HandleResult(GoogleSignInAccount result)
        {

            if (result != null)
            {
               
                Intent myIntent = new Intent(this, typeof(GoogleLoginActivity));
                myIntent.PutExtra("result", result);
                SetResult(Result.Ok, myIntent);
            }
            Finish();
        }

        private async void UpdateData(bool isSignedIn)
        {
            if (isSignedIn)
            {
                HandleResult(mAuth);
            }
            else
            {
                await System.Threading.Tasks.Task.Delay(2000);
                mShouldResolve = true;
                mGoogleApiClient.Connect();
            }
        }

        protected override void OnStart()
        {
            base.OnStart();
            mGoogleApiClient.Connect();
        }

        protected override void OnStop()
        {
            base.OnStop();
            mGoogleApiClient.Disconnect();
        }

        protected override void OnSaveInstanceState(Bundle outState)
        {
            base.OnSaveInstanceState(outState);
            outState.PutBoolean(KEY_IS_RESOLVING, mIsResolving);
            outState.PutBoolean(KEY_SHOULD_RESOLVE, mIsResolving);
        }

        protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
        {
            base.OnActivityResult(requestCode, resultCode, data);

            // Result returned from launching the Intent from GoogleSignInApi.getSignInIntent(...);
            if (requestCode == RC_SIGN_IN)
            {
                var result = Android.Gms.Auth.Api.Auth.GoogleSignInApi.GetSignInResultFromIntent(data);
                if (result.IsSuccess)
                {
                    // Google Sign In was successful, authenticate with Firebase
                    HandleResult(result.SignInAccount);

                }
                else
                {
                    // Google Sign In failed, update UI appropriately
                    // [START_EXCLUDE]
                    HandleResult(null);
                    // [END_EXCLUDE]
                }
            }
        }

        public void OnConnected(Bundle connectionHint)
        {
            UpdateData(false);
        }

        public void OnConnectionSuspended(int cause)
        {

        }

        public void OnConnectionFailed(ConnectionResult result)
        {
            if (!mIsResolving &amp;amp;&amp;amp; mShouldResolve)
            {
                if (result.HasResolution)
                {
                    try
                    {
                        result.StartResolutionForResult(this, RC_SIGN_IN);
                        mIsResolving = true;
                    }
                    catch (IntentSender.SendIntentException e)
                    {
                        mIsResolving = false;
                        mGoogleApiClient.Connect();
                    }
                }
                else
                {
                    ShowErrorDialog(result);
                }
            }
            else
            {
                UpdateData(false);
            }
        }

        class DialogInterfaceOnCancelListener : Java.Lang.Object, IDialogInterfaceOnCancelListener
        {
            public Action<IDialogInterface> OnCancelImpl { get; set; }

            public void OnCancel(IDialogInterface dialog)
            {
                OnCancelImpl(dialog);
            }
        }

        void ShowErrorDialog(ConnectionResult connectionResult)
        {
            int errorCode = connectionResult.ErrorCode;

            if (GoogleApiAvailability.Instance.IsUserResolvableError(errorCode))
            {

                var listener = new DialogInterfaceOnCancelListener();
                listener.OnCancelImpl = (dialog) =>
                {

                    mShouldResolve = false;
                };
                GoogleApiAvailability.Instance.GetErrorDialog(this, errorCode, RC_SIGN_IN, listener).Show();
            }
            else
            {
                mShouldResolve = false;
            }
            HandleResult(mAuth);
        }
    }

}

Recordad que al principio dijimos que os apuntarais un código que luego nos haría falta, es este que se usa para poder acceder a las cuentas de Google

GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DefaultSignIn)
              .RequestIdToken("2485447395-g5sdqpgfdjklgo2f1ir84s0cedsdqgv1.apps.googleusercontent.com")
                .Build();

En nuestra clase MainActivity debemos añadir el método OnActivityResult para poder recoger la llamada a esa nueva Activity que hemos creado:

protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
    base.OnActivityResult(requestCode, resultCode, data);
    if (requestCode == FirebaseAuthService.REQ_AUTH && resultCode == Result.Ok)
    {
        GoogleSignInAccount sg = (GoogleSignInAccount)data.GetParcelableExtra("result");
        MessagingCenter.Send(FirebaseAuthService.KEY_AUTH, FirebaseAuthService.KEY_AUTH, sg.IdToken);
    }
} 

Como veis hacemos uso del centro de mensajería de Xamarin, luego le daremos uso desde nuestro ViewModel.

Implementación del Servicio para Google Auth.

Volvemos a nuestro servicio e implementamos los métodos que nos faltaban que como veréis siguen la linea de los demás y son muy sencillitos:

 

public void SignInWithGoogle()
{
    var googleIntent = new Intent(Forms.Context, typeof(GoogleLoginActivity));
    ((Activity)Forms.Context).StartActivityForResult(googleIntent, REQ_AUTH);
}

 

public async Task<bool> SignInWithGoogle(string token)
{
    try
    {
        AuthCredential credential = GoogleAuthProvider.GetCredential(token, null);
        await Firebase.Auth.FirebaseAuth.GetInstance(MainActivity.app).SignInWithCredentialAsync(credential);
        return true;
    }
    catch (Exception ex)
    {
        
        return false;
    }
}

Pues con esto ya tenemos terminado nuestro servicio, como veis no ha tenido mucha complejidad, ahora vamos a pasar a implementar las llamadas desde el ViewModel.

Usando el Servicio desde el ViewModel

Lo primero será crear la inyección de dependencia desde nuestro ViewModel de Login, para ello en el constructor añadimos la siguiente linea:

private IFirebaseAuthService _firebaseService;

public LoginViewModel(IUserDialogs userDialogsService)
{
    _userDialogService = userDialogsService;
    _firebaseService = DependencyService.Get<IFirebaseAuthService>();
    MessagingCenter.Subscribe<String, String>(this, _firebaseService.getAuthKey(), (sender, args) =>
    {
        LoginGoogle(args);

    });
    
}

Con esto ya tenemos acceso a la implementación del servicio, como veis en el constructor también hemos hecho uso del centro de Mensajes y nos suscribimos al mensaje que lanza MainActivity cuando hay un login con una cuenta de Google.

El restro de los métodos como veremos a continuación son simplemente llamadas a los métodos del servicio y redirección a la página a la que queremos acceder:

 

private async Task LoginCommandExecute()
{
    if (await _firebaseService.SignIn(Username, Password))
    {
        await NavigationService.NavigateToAsync<MainViewModel>();
    }
    else
    {
        _userDialogService.Toast("Usuario o contraseña incorrectos");
    }
    
}
private async Task LoginGoogleCommandExecute()
{
        _firebaseService.SignInWithGoogle();

}

private async Task LoginGoogle(String token)
{
    if (await _firebaseService.SignInWithGoogle(token))
    {
        await NavigationService.NavigateToAsync<MainViewModel>();
    }

}

Pues ya está nuestra clase de Login completa, pero ¿cómo hacemos para que cuando ya se haya logado el usuario acceda directamente a la pantalla principal? 

Muy sencillo, sólo hay que modificar el método InitializeAsync de la clase NavigationService:

public Task InitializeAsync()
{
    var _firebaseService = DependencyService.Get<IFirebaseAuthService>();
    if (_firebaseService.IsUserSigned())
    {
        return NavigateToAsync<MainViewModel>();
    }
    else{
        return NavigateToAsync<LoginViewModel>(); 
    }
}

Cómo podeis comprobar, todo sigue la misma lógica:

  • Declaración del servicio por inyección de dependencias
  • Llamada al método correspondiente

Registro de usuarios

La parte de la implantación del registro de usuarios os la dejo como ejercicio, de todas formas la podéis encontrar ya implementada en el sitio de Github que luego os dejaré en enlace.

Conclusión

Espero que este tutorial sea de vuestro agrado y recordad que podéis descargar el estado actual del proyecto desde el siguiente enlace de Github: https://github.com/jamontes79/xamarin-forms-firebase-sample/tree/e5eb4e032be2a24d550e8bf5635f4e8a285a5fe7

Cualquier comentario o sugerencia será bienvenido como siempre.

Nos vemos en el siguiente artículo para implementar esto mismo en iOS.

Publicado en Desarrollo, Xamarin y etiquetado , , , , , .

Ingeniero Técnico en Informática de Sistemas. Apasionado de la tecnología y enfocado al desarrollo móvil

2 Comentarios

  1. Pingback: Firebase para aplicaciones Xamarin.Forms – Parte 2. FirebaseAuth para iOS – Alberto Montes de Oca

  2. Pingback: Firebase para aplicaciones Xamarin.Forms – Parte 3. FirebaseDataBase para Android – Alberto Montes de Oca

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *