Notificaciones Push con Firebase, Azure Function y MAUI

En esta publicación vamos a configurar un proyecto Firebase que sirva de centro de notificaciones push, adicionalmente vamos a crear una API con Azure Function que usaremos para enviar notificaciones y finalmente crearemos una aplicación Android usando MAUI que va a recibir los mensajes que son enviados por la API.

Infraestructura para Notificaciones Push
Infraestructura para Notificaciones Push

El flujo para enviar un mensaje sería similar al siguiente:

No quiero entrar en detalle de como funciona el servicio «Messaging» de Firebase; ya que la documentación se explica a detalle. Lo que por ahora necesitamos saber es que con este servicio podemos enviar mensajes directo a un cliente especifico o podemos crear filtros para enviarlo a un grupo de cliente que cumple con ciertas condiciones, ejemplo podemos crear un tópico y enviar un mensaje el cual será recibido solo por los clientes que están suscriptos a ese tópico. Este enfoque es el que vamos a usar en nuestro ejemplo.

Configuración de Firebase

Lo primero que necesitamos es hacer algunas configuraciones en la consola de Firebase para preparar todo lo necesario para enviar y recibir notificaciones push, por eso debemos crear un proyecto nuevo y registrar una aplicación para Android.

Crear un nuevo proyecto en Firebase

Necesitamos crear un nuevo proyecto en la consola de Firebase.

Empezar a Crear Proyecto en Firebase
Crear proyecto en la consola de Firebase

El primer paso es establecer el nombre del proyecto. Puede contener espacios si lo deseamos.

Nombre del Proyecto Firebase
Nombre del Proyecto Firebase

Luego se nos preguntara si deseamos configurar algunas opciones de analítica, eso no es necesario en nuestro caso, finalmente confirmamos la creación del proyecto.

Confirmar Creación del Proyecto Firebase
Confirmar Creación del Proyecto Firebase

Ahora se nos mostrará el proyecto creado y los servicios que tenemos disponibles.

Vista Proyecto Firebase Creado
Vista Proyecto Firebase Creado

Creación de la aplicación para Android en Firebase

Dentro del proyecto que creamos vamos a crear una nueva aplicación Android.

Empezar a Crear Aplicación Android en Firebase
Crear una Aplicación Android

El primer paso es definir el nombre del paquete de la aplicación, esto es muy importante que el nombre que indiquemos aquí debe ser el mismo nombre de paquete que indicamos en la aplicación Android. Opcionalmente, recomendamos colocar un sobrenombre de aplicación.

Vamos a omitir el certificado, ya que esta aplicación la vamos a instalar en nuestro teléfono Android para pruebas y no va a ser publicada en la tienda.

Registro aplicación Firebase
Registro aplicación Firebase

En siguiente paso nos dará la opción de descargar el archivo google-services.json que usaremos más tarde. Es importante descargarlo y guárdalo en un lugar seguro. Luego podemos dar siguiente hasta llegar al botón para confirmar el registro de la aplicación.

Descargar Google Services en Firebase
Descargar Google Services en Firebase

El archivo anterior será usado por la aplicación Android para su configuración. Adicionalmente, necesitamos una configuración para Firebase Admin SDK, el cual se usara nuestra aplicación servidor para poder enviar los mensajes a través de Firebase. Para esto debemos volver a las configuraciones generales del proyecto.

Firebase Configuraciones Generales
Firebase Configuraciones Generales

Luego debemos ir a la pestaña «Cuenta de Servicio». Y presionamos sobre el botón «Generar nueva clave privada».

Cuenta Servicio
Cuenta de Servicio

Firebase nos da la opción descargar la configuración y nos da una advertencia que guardemos bien el archivo porque no se va a poder descargar nuevamente el mismo archivo. Si lo pierdes debes generar uno nuevo y actualizar la información en la aplicación que crearemos más adelante.

Generar nueva clave privada
Generar nueva clave privada

¡Listo! Con esto ya hemos configurado todo lo requerido para comenzar a generar nuestra API y aplicación Android.

Creamos la Aplicación Android Usando MAUI para recibir las notificaciones push

Para este ejemplo estoy utilizando Microsoft Visual Studio Community 2022 (64 bits) – Versión 17.4.2 en español. Para mantener todo junto vamos a generar una solución en blanco y más adelantes agregaremos 2 proyectos, uno para Servidor que envía los mensajes(Azure Function) y otro para la aplicación móvil Android (MAUI). De igual manera puedes bajar el código completo en nuestro repositorio de GitHub.

Creemos la Solución en Blanco, vamos a nombrarlo como NotificationsPush.Maui.

Crear Solución en Blanco
Crear Solución en Blanco

Vamos a generar la aplicación Android y agregaremos una opción para suscribirse a un tópico de notificaciones llamados “Bienvenidos” para recibir todos los mensajes que sean enviados por el servidor, luego serán mostrados como notificaciones Push.

Ahora debemos hacer clic derecho sobre la solución y buscar la opción agregar Nuevo Proyecto.

Agregar proyecto MAUI
Agregar proyecto MAUI

Seleccionamos el tipo proyecto Aplicación .NET MAUI. Cuando solicite el nombre del proyecto puedes colocar el que desees, por ahora nosotros usaremos NotificationsPush.Maui.

Crear una aplicación maui
Crear una aplicación MAUI

Seleccionamos .NET 6 como versión del framework.

Version Framework
Versión Framework

Al final debería tener algo similar a lo siguiente.

Proyecto MAUI creado
Proyecto MAUI creado

Ahora necesitas agregar en la raíz del proyecto el archivo de configuraciones google-services.json que descargamos durante la configuración de la aplicación en Firebase.

Google Services Configuración
Google Services Configuración

Los cambios más complicados los vamos a hacer en el archivo NotificationsPush.Maui.csproj por eso trataremos de ir poco a poco, empezamos con ver el archivo original generado.

NotificationsPush.Maui.csproj
NotificationsPush.Maui.csproj

Como podemos ver tenemos etiquetas TargetFrameworks y SupportedOSPlatformVersion donde estamos indicando las plataformas que vamos a soportar. Lo primero que vamos a hacer es quitar el soporte a todas las plataformas, excepto la de Android (Porque este post solo enfoca esta).

Haremos los cambios necesarios para pasar de soportes para varias plataformas del archivo actual…

NotificationsPush.Maui.csproj con soporte a todas las plataformas
NotificationsPush.Maui.csproj con soporte a todas las plataformas

A solo soporte para Android, para esto es necesario remover todas las referencias hacia las demás plataformas.

NotificationsPush.Maui.csproj con soporte para Android
NotificationsPush.Maui.csproj con soporte para Android

Un paso muy importante es cambiar el ApplicationId, esto es porque es la variable que se usa para definir el nombre del paquete Android, debe coincidir con el mismo nombre que se configuró en el nombre de paquete de aplicación para Android, en nuestro caso es net.programemos.pushnotificaciones.

ApplicationId
Variable ApplicationId

Para finalizar necesitamos agregar estas 3 secciones de ItemGroup.

NotificationsPush.Maui.csproj
NotificationsPush.Maui.csproj

¿Y esto último para que es?

  • La primera sección es para instalar el paquete NuGet que vamos a utilizar llamado Plugin.Firebase (También lo puedes instalar desde gestor de paquetes NuGet igual que cualquier otro)
  • La segunda sección es para que la configuración de google-services.json solo se incluya cuando se compila para Android
  • La tercera es para incluir paquetes NuGet adicionales que solo son requeridos para Android

Con esto ya tendremos listo el archivo NotificationsPush.Maui.csproj.

Ahora, como solo estamos dando soporte a Android, debemos eliminar las carpetas de las plataformas que ya no vamos a utilizar.

Plataformas Disponibles
Plataformas Soportadas

El próximo archivo que vamos a editar será el AndroidManifest.xml vamos a agregar algunos permisos y configuraciones que permiten que la aplicación reciba notificaciones. El archivo original debería verse similar a esto:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
	<application android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true"></application>
	<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
	<uses-permission android:name="android.permission.INTERNET" />
</manifest>

Luego de agregar código requerido debería verse de la siguiente manera.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
	<application android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true">
		<receiver
			android:name="com.google.firebase.iid.FirebaseInstanceIdInternalReceiver"
			android:exported="false" />
		<receiver
			android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver"
			android:exported="true"
			android:permission="com.google.android.c2dm.permission.SEND">
			<intent-filter>
				<action android:name="com.google.android.c2dm.intent.RECEIVE" />
				<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
				<category android:name="${applicationId}" />
			</intent-filter>
		</receiver>
	</application>
	<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
	<uses-permission android:name="android.permission.INTERNET" />
</manifest>

Aprovechando que estamos en el proyecto de Android vamos a modificar el archivo MainActivity.cs originalmente el archivo debería verse así:

using Android.App;
using Android.Content.PM;
using Android.OS;
namespace NotificationsPush.Maui
{
    [Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
    public class MainActivity : MauiAppCompatActivity
    {
    }
}

Vamos a agregar algunas funciones que nos van a permitir manejar las notificaciones cuando son recibidas.

using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.OS;
using Plugin.Firebase.CloudMessaging;
namespace NotificationsPush.Maui
{
    [Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
    public class MainActivity : MauiAppCompatActivity
    {
        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);
            HandleIntent(Intent);
            CreateNotificationChannelIfNeeded();
        }
        protected override void OnNewIntent(Intent intent)
        {
            base.OnNewIntent(intent);
            HandleIntent(intent);
        }
        private static void HandleIntent(Intent intent)
        {
            FirebaseCloudMessagingImplementation.OnNewIntent(intent);
        }
        private void CreateNotificationChannelIfNeeded()
        {
            if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
            {
                CreateNotificationChannel();
            }
        }
        private void CreateNotificationChannel()
        {
            var channelId = $"{PackageName}.general";
            var notificationManager = (NotificationManager)GetSystemService(NotificationService);
            var channel = new NotificationChannel(channelId, "General", NotificationImportance.Default);
            notificationManager.CreateNotificationChannel(channel);
            FirebaseCloudMessagingImplementation.ChannelId = channelId;
        }
    }
}

Ahora debemos modificar el archivo MauiProgram.cs, el archivo original debería verse similar a este.

namespace NotificationsPush.Maui
{
    public static class MauiProgram
    {
        public static MauiApp CreateMauiApp()
        {
            var builder = MauiApp.CreateBuilder();
            builder
                .UseMauiApp<App>()
                .ConfigureFonts(fonts =>
                {
                    fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
                    fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
                });
            return builder.Build();
        }
    }
}

Luego de los cambios para hacer registro del servicio de notificaciones quedaría algo similar a lo siguiente:

using Microsoft.Maui.LifecycleEvents;
using Plugin.Firebase.Auth;
using Plugin.Firebase.Shared;
using Plugin.Firebase.Android;
namespace NotificationsPush.Maui
{
    public static class MauiProgram
    {
        public static MauiApp CreateMauiApp()
        {
            var builder = MauiApp.CreateBuilder();
            builder
                .UseMauiApp<App>()
                .RegisterFirebaseServices()
                .ConfigureFonts(fonts =>
                {
                    fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
                    fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
                });
            return builder.Build();
        }
        private static MauiAppBuilder RegisterFirebaseServices(this MauiAppBuilder builder)
        {
            builder.ConfigureLifecycleEvents(events =>
            {
                events.AddAndroid(android => android.OnCreate((activity, state) =>
                    CrossFirebase.Initialize(activity, state, CreateCrossFirebaseSettings())));
            });
            builder.Services.AddSingleton(_ => CrossFirebaseAuth.Current);
            return builder;
        }
        private static CrossFirebaseSettings CreateCrossFirebaseSettings()
        {
            return new CrossFirebaseSettings(isAuthEnabled: true, isCloudMessagingEnabled: true);
        }
    }
}

¿Qué hicimos en estos últimos pasos?

Todo lo que hicimos hasta ahora es para configurar el servicio y procesos que manejaran las notificaciones en Android. Estos pasos siempre los debemos hacer para preparar nuestra aplicación, para trabajar con notificaciones, en todos nuestras aplicaciones será igual.

Lo que vamos a hacer a continuación ya son las modificaciones propias de nuestra aplicación (De nuestro negocio) ya será nuestra lógica personalizada. Crearemos una interfaz de usuario con 2 botones, uno será para suscribirnos a un tópico de notificaciones llamado «Bienvenido» y otro botón para cancelar la suscripción y dejar de recibir mensajes.

Vamos a crear la interfaz gráfica con la que va a interactuar el usuario, abrimos el archivo MainPage.xaml y agregamos el siguiente código.

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
    x:Class="NotificationsPush.Maui.MainPage"
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
    <ScrollView>
        <VerticalStackLayout
            Padding="30,0"
            Spacing="25"
            VerticalOptions="Center">
            <Image
                HeightRequest="200"
                HorizontalOptions="Center"
                Source="dotnet_bot.png" />
            <Label
                x:Name="Status"
                FontSize="32"
                HorizontalOptions="Center"
                SemanticProperties.HeadingLevel="Level1"
                Text="No estas suscrito" />
            <Button
                x:Name="SubscribeBtn"
                Clicked="OnSubscribeClicked"
                HorizontalOptions="Center"
                Text="Subscribe" />
            <Button
                x:Name="UnSubscribeBtn"
                Clicked="OnUnSubscribeClicked"
                HorizontalOptions="Center"
                Text="Unsubscribe" />
        </VerticalStackLayout>
    </ScrollView>
</ContentPage>

Con el código anterior creamos los 2 botones que mencionamos anteriormente, adicionalmente se incluyo una etiqueta para saber el estatus de la suscripción y una imagen solo para que se vea más agradable. Ya tenemos la interfaz de usuario, ahora es hora definir la lógica que hará que la interfaz de usuario funcione, vamos al archivo MainPage.xaml.cs y agregamos lo siguiente:

    private async void OnSubscribeClicked(object sender, EventArgs e)
    {
        await CrossFirebaseCloudMessaging.Current.CheckIfValidAsync();
        await CrossFirebaseCloudMessaging.Current.SubscribeToTopicAsync("Bienvenido");
        this.Status.Text = "Ahora estas suscrito";
        await DisplayAlert("Bienvenido", "Te has suscrito al tema de Bienvenido", "OK");
    }
    private async void OnUnSubscribeClicked(object sender, EventArgs e)
    {
        await CrossFirebaseCloudMessaging.Current.UnsubscribeFromTopicAsync("Bienvenido");
        this.Status.Text = "Ya no estas suscrito";
        await DisplayAlert("Bienvenido", "Te has dado de baja del tema de Bienvenido", "OK");
    }

En el código anterior creamos los eventos que ocurren al presionar los botones, puedes ver que se usa la librería de Firebase para suscribirse al tópico para escuchar los mensajes, también observamos que en ningún momento estamos creando algún código para gestionar notificación, todo esto fue lo que sé en los archivos dentro de Android debido a que este ocurre en segundo plano.

¡Felicitaciones! Has terminado de crear una aplicación Android con la capacidad de recibir notificaciones, si corremos la aplicación veremos algo similar a lo siguiente:

Para probar las notificaciones push debes hacerlo en un dispositivo físico real. En emulador nunca va a funcionar (Algunos dicen que si instalas Google Play las notificaciones push funcionarán en el emulador, pero yo en lo personal nunca hice la prueba).

Aplicación Android
Aplicación Android

Creamos el Servidor para el envío de Notificaciones Push

Estamos listo para recibir las notificaciones en nuestra aplicación Android, ahora es hora de crear una API que nos permita enviar los mensajes. Será bastante sencillo, simplemente tendrá un endpoint que usaremos para enviar las notificaciones. Lo primero que necesitamos es agregar un proyecto de Azure Functions a nuestra solución. En nuestro caso lo llamaremos NotificationsPush.Server. En este caso usamos NET 7.

Azure Function Proyecto
Azure Function Proyecto

Lo primero que vamos a hacer es instalar NuGet requerido para trabajar con el SDK de Firebase llamado «FirebaseAdmin». En este ejemplo vamos a usar la versión 2.3.0.

Nuget FirebaseAdmin
Nuget FirebaseAdmin

Ahora debemos hacer es configurar nuestra aplicación con el archivo generado al crear las llaves en Firebase y descargamos previamente, Esta configuración lo vamos a hacer en «Program.cs». El SDK nos da varias opciones para su configuración usando el archivo JSON previamente descargado.

Por simplificar vamos a colocar las configuraciones directamente en código (Esto es una mala práctica, pero lo hago para mantener el ejemplo simple). Los datos mostrados en las configuraciones fueron modificados y no son reales, solo los dejos de referencia. Debe reemplazar los datos con la configuración del archivo JSON descargado en la configuración de llaves para SDK.

using FirebaseAdmin;
using Google.Apis.Auth.OAuth2;
using Microsoft.Extensions.Hosting;
var host = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults()
    .Build();
string jsonData = @"{
                  'type': 'service_account',
                  'project_id': 'message-push-195b5',
                  'private_key_id': '7673e1b3e4aa63aacdef2a2a83a59c4d92c40a33',
                  'private_key': '-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDnl00iNzSwJjkk\nibYzeUP6t67Q316raLXD8wQs7ovev+aW0s8E3e77HWxZUKD5wIvJRsIWCEUkfA72\n2g+smPNMq+X/AKR2Q7zPxqCWR6BrHCyA4unGDmVF1vDCEGM7q2M4IDNkHuEkl4Ig\nwcPIldrRD3Hz01K8JwZ6FOFOMJpFUM9uT8ZHa5WBpzyWVN15WHGDpTTiGq1IRw2W\n9irtzdlxTNKAriQ2nyus5AMGlvEF1V984IZbcSNQTvTpg37c2reBI4FwTULRP6bs\ncQo1vcQjDBXdat9b71/+wg==\n-----END PRIVATE KEY-----\n',
                  'client_email': 'firebase-adminsdk-bpkgb@message-push-195b5.iam.gserviceaccount.com',
                  'client_id': '888879189536215730976',
                  'auth_uri': 'https://accounts.google.com/o/oauth2/auth',
                  'token_uri': 'https://oauth2.googleapis.com/token',
                  'auth_provider_x509_cert_url': 'https://www.googleapis.com/oauth2/v1/certs',
                  'client_x509_cert_url': 'https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-bpkgb%40message-push-195b5.iam.gserviceaccount.com'
                }";
if (FirebaseApp.DefaultInstance == null)
{
    FirebaseApp.Create(new AppOptions()
    {
        Credential = GoogleCredential.FromJson(jsonData)
    });
}
host.Run();

Lo que hacemos en código anterior es carga la configuración requerida por el SDK de Firebase y la establecemos sin aún no existe una configuración anterior, ahora creamos un nuevo archivo llamado «Notifications.cs» donde colocaremos la función que será el endpoint que usaremos para enviar mensajes. Luego reemplazamos su contenido con el siguiente.

namespace NotificationsPush.Server;
using System.Net;
using FirebaseAdmin.Messaging;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
public class NotificationMessage
{
    public string Message { get; set; }
}
public class Notifications
{
    private readonly ILogger logger;
    public Notifications(ILoggerFactory loggerFactory)
    {
        this.logger = loggerFactory.CreateLogger<Notifications>();
    }
    [Function("SendNotifications")]
    public async Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Function, "post", Route = "notificaciones")] HttpRequestData req)
    {
        this.logger.LogInformation("Preparando el envio del mensaje...");
        StreamReader sr = new(req.Body);
        var notificationMessage = JsonConvert.DeserializeObject<NotificationMessage>(sr.ReadToEnd());
        var message = new Message()
        {
            Topic = "Bienvenido",
            Notification = new Notification()
            {
                Title = "Bienvenido",
                Body = notificationMessage.Message ?? "Bienvenido usuario",
            }
        };
        string result = await FirebaseMessaging.DefaultInstance.SendAsync(message);
        this.logger.LogInformation($"Respuesta: {result}");
        var response = req.CreateResponse(HttpStatusCode.OK);
        response.Headers.Add("Content-Type", "text/plain; charset=utf-8");
        response.WriteString("Mensaje enviado");
        return response;
    }
}

El código anterior crea un endpoint que recibe un campo llamado «Message» donde establecemos el mensaje de texto que deseamos enviar a nuestro usuario, el SDK nos permite varias maneras de segmentar a cuáles usuarios llegara el mensaje. Nosotros lo vamos a hacer el tópico llamado Bienvenido (línea 64).

¡Felicitaciones! Has creado un servidor para envío de notificaciones push a cliente Android. Esto es el ejemplo básico, pero puedes personalizarlo de la manera que desees o necesites. Ahora es momento de probar todo junto.

Momentos de probar nuestro flujo de notificaciones push

Debemos pasar nuestra aplicación MAUI a nuestro celular. Hay varias maneras, la más fácil es activar el modo desarrollador de Android y usar el cable USB para depurar la aplicación desde Visual Studio en nuestro teléfono.

Cuando la aplicación esté corriendo en el móvil debemos presionar sobre el botón de suscribirse.

Aplicación Android
Aplicación Android

Ahora ejecutamos la aplicación servidor que es nuestra API para envío de mensajes.

Aplicación Servidor Ejecutando
Aplicación Servidor Ejecutando

Ahora debemos un enviar una solicitud a nuestro servidor. La opción más fácil es usar Postman. Debemos comprobar que en la consola nos muestre que el mensaje se envió correctamente.

Enviar Mensaje con Postman al Servidor
Enviar Mensaje con Postman al Servidor

Ahora verificamos en nuestro teléfono que el mensaje fue recibido.

Notificación Recibida en el Móvil
Notificación Recibida en el Móvil

¡Felicitaciones! Creaste un flujo de envío y recepción de notificaciones push, usando Firebase, Azure Function, MAUI. Esto es la base, de aquí en adelante puedes agregar y personalizar todo lo que necesites, por ejemplo agregar una imagen para se vea más agradable el mensaje, ejecutar una acción al hacer clic sobre la notificación push, entre muchas otras cosas.

Conclusión

El uso de notificaciones push es algo fundamental en toda aplicaciones móvil, nos permiten mantener una comunicación con los usuarios, además es una gran herramienta de negocio si se usa correctamente, los servicios y herramientas que vimos permiten una implementar sencilla de esto, permitiéndonos concentrarnos en lo que más importa; nuestra aplicación y nuestros usuarios.

Recuerda que el código completo lo puedes descargar de mi GitHub. Te invito a ver mis otras publicaciones.