Alert dialog personalizado con Xamarin Forms

  • xamarin-forms

Es este post, vamos a ver cómo crear un dialogo de alerta personalizado. Para el ejemplo, se crearán dos casos, uno para mostrar un mensaje informativo y otro para mostrar un mensaje que requiera confirmación.

Gif en el que se muestra una demo del ejemplo que se va a crear, con dos diálogos de alerta, uno informativo y otro de confirmación

Para mostrar los 2 tipos de diálogos, se ha creado un servicio con la siguiente interfaz.

public interface IAlertDialogService
{
    Task ShowDialogAsync(string title, string message, string close);
    Task<bool> ShowDialogConfirmationAsync(string title, string message, string cancel, string ok);
}

Antes de ir con la implementación de la interfaz, vamos a ver cómo crear el dialogo de alerta, que será llamado desde dicha implementación. Este dialogo, no es más que un popup creado con la ayuda del nuget Rg.Plugins.Popup, que recibe la información que tiene que mostrar (título, mensaje, acciones, etc…). Este nuget requiere de una mínima configuración, basta con inicializarlo desde el AppDelegate y el MainActivitys, para iOS y Android, respectivamente

Además, para informar de resultado, es decir, si el usuario pulsa el botón ok o el cancelar/cerrar, el dialogo recibe un callback, esto es, un método al que llama para informar del resultado.

private Func callback;
 public AlertDialogPopup(string title, string message, string cancel, string ok, Func callback)
 {
     this.callback = callback;
 InitializeComponent(); LbTitle.Text = title; LbMessage.Text = message; BtCancel.IsVisible = !string.IsNullOrWhiteSpace(cancel); BtOk.Text = ok; BtCancel.Text = cancel;
 }
 private async void BtCancel_Clicked(object sender, EventArgs e)
 {
     await callback.Invoke(false);
 }
 private async void BtOk_Clicked(object sender, EventArgs e)
 {
     await callback.Invoke(true);
 }
 private async void BtClose_Clicked(object sender, EventArgs e)
 {
     await callback.Invoke(false);
 }

El diseño de este popup consta de un frame, con los bordes redondeados, que contiene los distintos botones y texto. La gracia de personalizar los diálogos de alerta de esta manera, es que podemos darles el aspecto que nos interese (incluir logos, crear animaciones, etc…)

<?xml version="1.0" encoding="utf-8" ?>
<rx:PopupPage xmlns="http://xamarin.com/schemas/2014/forms"
              xmlns:rx="clr-namespace:Rg.Plugins.Popup.Pages;assembly=Rg.Plugins.Popup"
                xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                x:Class="XamarinCustomAlertDialog.Services.AlertDialogService.AlertDialogPopup">

    <Frame x:Name="FrContent" Grid.ColumnSpan="2" Grid.RowSpan="3"
           BackgroundColor="WhiteSmoke"
           BorderColor="Black"
           CornerRadius="10"
           Margin="20"
           HorizontalOptions="FillAndExpand" VerticalOptions="Center"
           Opacity="0">
        <Grid x:Name="GrContent" RowDefinitions="50, Auto, 50"
              ColumnDefinitions="*, *"
              RowSpacing="10"
              ColumnSpacing="5"
              Padding="0"
              Margin="0"
              HorizontalOptions="FillAndExpand" VerticalOptions="Center">

            <Label x:Name="LbTitle" Grid.ColumnSpan="2"
                   FontAttributes="Bold"
                   FontSize="Large"
                   Margin="0,0,50,0"
                   MaxLines="1"
                   LineBreakMode="TailTruncation"
                   HorizontalTextAlignment="Start" VerticalTextAlignment="Center"
                   HorizontalOptions="FillAndExpand" VerticalOptions="Center" />

            <Label x:Name="LbMessage" Grid.Row="1" Grid.ColumnSpan="2"
                   FontSize="Body"
                   HorizontalOptions="FillAndExpand" VerticalOptions="Start" />

            <ImageButton x:Name="BtClose" Grid.Row="0" Grid.Column="1"
                         BackgroundColor="Transparent"
                         Source="close.png"
                         Aspect="AspectFit"
                         HeightRequest="48"
                         Margin="{OnPlatform Android=0, iOS=-15}"
                         HorizontalOptions="End" VerticalOptions="Start"
                         Clicked="BtClose_Clicked" />

            <Button x:Name="BtCancel" Grid.Row="2" Grid.Column="0"
                    BackgroundColor="Red"
                    TextColor="White"
                    HeightRequest="48"
                    CornerRadius="24"
                    Padding="5"
                    HorizontalOptions="FillAndExpand" VerticalOptions="End"
                    Clicked="BtCancel_Clicked"/>

            <Button x:Name="BtOk" Grid.Row="2" Grid.Column="1"
                    BackgroundColor="Green"
                    TextColor="White"
                    HeightRequest="48"
                    CornerRadius="24"
                    Padding="5"
                    HorizontalOptions="FillAndExpand" VerticalOptions="End"
                    Clicked="BtOk_Clicked"/>
        </Grid>
    </Frame>
</rx:PopupPage>

Además, en este ejemplo, se sobre-escribe el método OnSizeAllocated para dar al frame el tamaño que nos interesa, adaptándolo a su contenido.

protected override void OnSizeAllocated(double width, double height)
 {
     base.OnSizeAllocated(width, height);
 FrContent.HeightRequest = LbMessage.Height + 100 + FrContent.Padding.HorizontalThickness + GrContent.Margin.HorizontalThickness; FrContent.Opacity = 1;
 }

Por último, queda implementar la interfaz IDialogService. En este punto, ya estamos en disposición de hacerlo porque ya se ha creado el dialogo que vamos a utilizar.

Como los método a implementar retornan una tarea con el resultado de la ejecución, esto es, la acción realizada por el usuario. Se ha creado un TaskCompletionSource, a la que se le asigna el resultado desde el callback invocado desde el dialogo. Esto se ha conseguido con el siguiente código.

public class AlertDialogService : IAlertDialogService
{
    private TaskCompletionSource<bool> taskCompletionSource;
    private Task<bool> task;

    public async Task ShowDialogAsync(string title, string message, string close)
    {
        taskCompletionSource = new TaskCompletionSource<bool>();
        task = taskCompletionSource.Task;

        AlertDialogPopup alertDialog = new AlertDialogPopup(title, message, null, close, Callback);
        await Application.Current.MainPage.Navigation.PushPopupAsync(alertDialog);
        await task;
    }

    public async Task<bool> ShowDialogConfirmationAsync(string title, string message, string cancel, string ok)
    {
        taskCompletionSource = new TaskCompletionSource<bool>();
        task = taskCompletionSource.Task;

        AlertDialogPopup alertDialog = new AlertDialogPopup(title, message, cancel, ok, Callback);
        await Application.Current.MainPage.Navigation.PushPopupAsync(alertDialog);

        return await task;
    }

    private async Task Callback(bool result)
    {
        await Application.Current.MainPage.Navigation.PopPopupAsync();
        if (!taskCompletionSource.Task.IsCanceled &&
            !taskCompletionSource.Task.IsCompleted &&
            !taskCompletionSource.Task.IsFaulted)
        {
            taskCompletionSource.SetResult(result);
        }
    }
}

Con todo lo anterior, ya tenemos nuestro dialogo de alerta totalmente personalizado.

Como siempre, aquí os dejo un enlace al repositorio del proyecto de ejemplo que he creado https://github.com/jorgediegocrespo/XamarinCustomAlertDialog