Mostrando las entradas con la etiqueta Visual Studio. Mostrar todas las entradas
Mostrando las entradas con la etiqueta Visual Studio. Mostrar todas las entradas

miércoles, 1 de abril de 2020

Xamarin.Forms - Notificaciones locales

Seguimos aprendiendo y profundizando nuestros conocimientos en desarrollo móvil con Xamarin.Forms, este fin de semana se ha revisando y desarrollo algunos ejercicios prácticos acerca de las notificaciones locales haciendo uso de algunas funcionalidades de reloj del dispositivo móvil y la clase DependencyService que es un localizador de servicios que habilita las aplicaciones desde Xamarin.Forms con el fin de invocar la funcionalidad nativa de la plataforma desde código compartido.

Considerando que las notificaciones locales son alertas enviadas por aplicaciones instaladas en nuestros dispositivo móvil y menudo se usan para funciones como:

👦 Recordatorios.
📅 Eventos del calendario.
📌 Ubicaciones basado en geolocalización

DependencyService
Es el servicio de dependencia que permite acceder a funcionalidades nativas de cada plataforma con el fin de resolver la implementación de una capa interfaz sencilla. En el siguiente diagrama, se muestra cómo se invoca la funcionalidad nativa de la plataforma en una aplicación de Xamarin.Forms:

Información general de la ubicación del servicio mediante la clase DependencyService de Xamarin.Forms
Diagrama referencial de https://docs.microsoft.com
Para poder profundizar el concepto acerca de DependencyService sugiero revisar el siguiente link:


Lo interesante de todo esto es que Xamarin.Forms también nos facilita el uso de Plugins, para realizar este tipo de funcionalidad sin tener que codificar en cada plataforma, los más usados son:

👍 Xam.Plugin.Badge
👍 Plugin.Notifications

Más adelante iremos revisando y creando ejemplos prácticos con ambos plugins, por ahora implementaremos el ejemplo práctico apoyándonos con DependencyService y de este modo ir potenciando nuestra curva de aprendizaje en Xamarin.Forms; empecemos.

Requisitos:
Microsoft Visual Studio Community 2019.
Xamarin.Froms - XAML.
DependencyService.

Para lo cual iniciamos creando nuestro proyecto de nombre LocalNotifications de tipo Aplicación Móvil (Xamarin.Forms), seleccionamos la plantilla En Blanco y marcamos para las Plataformas de Android y iOS.

Luego agregaremos un elemento de tipo interfaz de nombre INotification.cs y una clase con el nombre de Notification.cs en el proyecto principal LocalNotifications. Finalmente agregamos clase AndroidNotification en el proyecto LocalNotifications.Android.

Diseño de Interfaz:
En el archivo XAML de nombre MainPage.xaml agregaremos los siguiente elementos:

Entry x:Name="_entryMessage": Será el encargado de determinar el texto o mensaje a notificar.

TimePicker x:Name="_timeExecute": Encargado de definir la hora para ejecutar la notificación local.

Switch x:Name="_switchActive": Define si se envía o no la notificación de acuerdo a lo ingresado y según la hora definido; es decir en caso este inactivo se ejecuta la notificación de acuerdo a la HORA definido, caso contrario a pesar de estar en la HORA definido y NO este activo, la notificación no se ejecutará.

Los demás elementos son de tipo Label que son etiquetas estáticas como completo de nuestra interfaz de diseño con XAML.

Para este ejemplo práctico todas las pruebas se viene realizando en un equipo móvil físico con Sistema Operativo Android, aclarando que esto no restringe el uso de emuladores y recordar que sólo se viene implementado por ahora los proyectos para la plataforma de Android.

XAML completo del archivo SignUpLogin.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             mc:Ignorable="d"
             x:Class="LocalNotifications.MainPage"
             Title="Notifications"
             Padding="10">
    <StackLayout Margin="0,35,0,0"
                 x:Name="stackLayout">
        <Label Text="Crear notificación"
               TextColor="#424949"
               HorizontalOptions="Center"
               VerticalOptions="Start"
               FontSize="Large"/>
        <Entry x:Name="_entryMessage"
               Placeholder="Ingresa en mensaje o texto a notificar..."
               FontSize="16"/>
        <StackLayout HorizontalOptions="FillAndExpand" Orientation="Horizontal">
            <Label Text="Determinar Hora"
                   TextColor="#424949"
                   HorizontalOptions="StartAndExpand"
                   FontSize="16"
                   VerticalOptions="Center"/>
            <TimePicker x:Name="_timeExecute"
                    Time="12:00:00"
                    Format="t"
                    PropertyChanged="OnTimePickerPropertyChanged" />
        </StackLayout>
        <StackLayout HorizontalOptions="FillAndExpand" Orientation="Horizontal">
            <Label Text="Activar Notificación"
                   TextColor="#424949"
                   HorizontalOptions="StartAndExpand"
                   FontSize="16"
                   VerticalOptions="Center"/>
            <Switch x:Name="_switchActive"
                    HorizontalOptions="EndAndExpand"
                    Toggled="OnSwitchToggled" />
        </StackLayout>
    </StackLayout>
</ContentPage>

Implementar el CodeBehind:
En la interfaz INotification.cs ya creada en el proyecto LocalNotifications e ingresamos las siguiente instrucción:

using System;
namespace LocalNotifications
{
    public interface INotification
    {
        /// <summary>
        /// Delegado nulo que servira para retorna el valor de la notificación a nuestro intefaz del XAML MainPage
        /// </summary>
        event EventHandler NotificationReceived;

        /// <summary>
        /// Método para inicializar la creación del canal de notificación en base a la versión del SDK
        /// </summary>
        void Initialize();

        /// <summary>
        /// Método para definir el horario de ejecución a notificar
        /// </summary>
        /// <param name="title"></param>
        /// <param name="message"></param>
        /// <returns></returns>
        int ScheduleNotification(string title, string message);

        /// <summary>
        /// Método para mostrar la notificación en la interfaz creada
        /// </summary>
        /// <param name="title"></param>
        /// <param name="message"></param>
        void ReceiveNotification(string title, string message);
    }
}

Luego creamos los siguiente atributos de tipo publico de nuestra clases o entidad Notification.cs ubicado en el proyecto  LocalNotifications:

using System;
namespace LocalNotifications
{
    public class Notification : EventArgs
    {
        public string Title { get; set; }
        public string Message { get; set; }
    }
}

Para finalizar el desarrollo en las 3 clases creadas, se implementará la interfaz de cada método creado en la clase AndroidNotification del proyecto LocalNotifications.Android:

using System;
using Android.App;
using Android.Content;
using Android.Graphics;
using Android.OS;
using Android.Support.V4.App;
using Xamarin.Forms;
using AndroidApp = Android.App.Application;

[assembly: Dependency(typeof(LocalNotifications.Droid.AndroidNotification))]
namespace LocalNotifications.Droid
{
    public class AndroidNotification : INotification
    {
        const string channelId = "default";
        const string channelName = "Default";
        const string channelDescription = "The default channel for notifications.";
        const int pendingIntentId = 0;

        public const string TitleKey = "title";
        public const string MessageKey = "message";

        bool channelInitialized = false;
        int messageId = -1;
        NotificationManager manager;

        public event EventHandler NotificationReceived;

        public void Initialize()
        {
            CreateNotificationChannel();
        }

        public int ScheduleNotification(string title, string message)
        {
            if (!channelInitialized)
            {
                CreateNotificationChannel();
            }

            messageId++;

            Intent intent = new Intent(AndroidApp.Context, typeof(MainActivity));
            intent.PutExtra(TitleKey, title);
            intent.PutExtra(MessageKey, message);

            PendingIntent pendingIntent = PendingIntent.GetActivity(AndroidApp.Context, pendingIntentId, intent, PendingIntentFlags.OneShot);

            NotificationCompat.Builder builder = new NotificationCompat.Builder(AndroidApp.Context, channelId)
                .SetContentIntent(pendingIntent)
                .SetContentTitle(title)
                .SetContentText(message)
                .SetLargeIcon(BitmapFactory.DecodeResource(AndroidApp.Context.Resources, Resource.Drawable.xamagonBlue))
                .SetSmallIcon(Resource.Drawable.xamagonBlue)
                .SetDefaults((int)NotificationDefaults.Sound | (int)NotificationDefaults.Vibrate);

            Android.App.Notification notification = builder.Build();
            manager.Notify(messageId, notification);

            return messageId;
        }

        public void ReceiveNotification(string title, string message)
        {
            var args = new Notification()
            {
                Title = title,
                Message = message,
            };
            NotificationReceived?.Invoke(null, args);
        }

        void CreateNotificationChannel()
        {
            manager = (NotificationManager)AndroidApp.Context.GetSystemService(AndroidApp.NotificationService);

            if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
            {
                var channelNameJava = new Java.Lang.String(channelName);
                var channel = new NotificationChannel(channelId, channelNameJava, NotificationImportance.Default)
                {
                    Description = channelDescription
                };
                manager.CreateNotificationChannel(channel);
            }

            channelInitialized = true;
        }
    }
}

Recordemos además que debemos inicializar la aplicación y el DependencyService desde el CodeBehind del archivo App.xaml.cs del proyecto LocalNotifications:

        public App()
        {
            InitializeComponent();
            DependencyService.Get<INotification>().Initialize();
            MainPage = new MainPage();
        }

Ahora se implementará las siguientes instrucciones en el CodeBehind del archivo MainPage.xaml.cs y de este modo dar funcionalidad a todo lo desarrollado:

El método OnTimerNotification es quien realmente se encarga de realizar la notificación, es decir es la interfaz que se encarga de inicializar la ejecución de la notificación de acuerdo a la hora definido y además es el encargado de secuenciar todo el canal de ejecución en base al método  CreateNotificationChannel, recuerda que para ello debe determina la invocación o implementación de DependencyService al carga el MainPage.xaml.cs.

Los demás métodos o instrucciones cuenta con las explicación en cabecera de cada uno de ellos (sección <summary>).

using System;
using System.ComponentModel;
using Xamarin.Forms;

namespace LocalNotifications
{
    public partial class MainPage : ContentPage
    {
        INotification notification;
        int notifNumber = 0;
        DateTime _triggerTime;

        public MainPage()
        {
            InitializeComponent();

            //Inicializa la referencia a la interfaz para poder ejecutar la notificación
            notification = DependencyService.Get<INotification>();

            //Se encarga de recuperar los datos de la notificación para mostrar en pantalla
            notification.NotificationReceived += (sender, eventArgs) =>
            {
                var evtData = (Notification)eventArgs;
                ShowNotification(evtData.Title, evtData.Message);
            };

            //Se encarga de iniciar la ejecucón de la notificación según el horario definido
            Device.StartTimer(TimeSpan.FromSeconds(1), OnTimerNotification);           
        }

        #region notification
        /// <summary>
        /// Metodo para la ejecucón de la notificación
        /// </summary>
        /// <returns></returns>
        bool OnTimerNotification()
        {
            if (_switchActive.IsToggled && DateTime.Now >= _triggerTime)
            {
                _switchActive.IsToggled = false;//Se desactiva con el fin de poder crear otra notificación                                
                notifNumber++;
                string title = $"Notificación N° {notifNumber}";
                string message = _entryMessage.Text               
notification.ScheduleNotification(title, message); //Interfaz que se encarga de inicializar la ejecución de la notificación de acuerdo a la hora definido y secuenciar todo el canal de ejecución              
            }
            return true;
        }

        /// <summary>
        /// Recuperar los datos de la notificación ejecutada
        /// </summary>
        /// <param name="title"></param>
        /// <param name="message"></param>
        void ShowNotification(string title, string message)
        {
            Device.BeginInvokeOnMainThread(() =>
            {
                var msg = new Label()
                {
                    Text = $"Notificación recibida:\nTitle: {title}\nMessage: {message}"
                };
                stackLayout.Children.Add(msg);
            });
        }
        #endregion

        #region timeExecute
        /// <summary>
        /// Método encargado de capturar el tiempo del elemento TimePicker x:Name="_timeExecute"
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        void OnTimePickerPropertyChanged(object sender, PropertyChangedEventArgs args)
        {
            if (args.PropertyName == "Time")
            {
                SetTriggerTime();
            }
        }

        /// <summary>
        /// Captura el valor de la propiedad Toggled del elemento Switch x:Name="_switchActive" de acuerdo a la HORA de ejecución DEFINIDO
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        void OnSwitchToggled(object sender, ToggledEventArgs args)
        {
            SetTriggerTime();
        }

        /// <summary>
        /// Método que DETERMINA o DEFINE el tiempo de ejecución en base a la PROPIEDAD OnTimePickerPropertyChanged del elemento TimePicker x:Name="_timeExecute"
        /// </summary>
        void SetTriggerTime()
        {
            if (_switchActive.IsToggled)
            {
                _triggerTime = DateTime.Today + _timeExecute.Time;
                if (_triggerTime < DateTime.Now)
                {
                    _triggerTime += TimeSpan.FromDays(1);
                }
            }
        }
        #endregion
    }
}

Resultado:
Comparto el vídeo 📹 acerca de lo desarrollado de acuerdo a la descripción de este post publicado, las pruebas fueron validados en plataforma 📱 para Android.

Descarga la fuente de:
En proceso de carga...
Gratitud a Dios nuevamente😊y gracias a todos ustedes por la acogida de este nuevo post, éxitos y bendiciones 🙏 y un gran abrazo a todos, y cuidarse como siempre ✌...!!!