Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile / Xamarin

Custom Dialogs in Xamarin.Forms: Plugin Rg.Plugins.Popup Pages Asynchronous

5.00/5 (4 votes)
8 Dec 2019CPOL1 min read 16.3K  
This article proposes a solution for plugging Rg.Plugins.Popup asynchronous into your code.

Introduction

This article proposes a solution for plugging "Rg.Plugins.Popup" package into a Xamarin.Forms application.

Background

The 3rd party package is a custom dialog provider. Below, I will demonstrate how to call its pages from a DialogService asynchronous. The solution is in using TaskCompletionSource for external asynchronous operation.

The state of a task created by a TaskCompletionSource is controlled explicitly by the methods on TaskCompletionSource. This enables the completion of the external asynchronous operation to be propagated to the underlying Task. The separation also ensures that consumers are not able to transition the state without access to the corresponding TaskCompletionSource. [From MS doc]

Result

Image 1

Confirmation Dialog

Image 2

Ok Dialog

IDialogService Implementation

The following is a code snippet from DialogService.cs file.

C#
public async Task<bool> ShowConfirmationDialogAsync
    (string message, string yesButtonText = "Yes", string noButtonText = "No")
{
     var confirmationDialog = new ConfirmationDialog(message)
     {
         YesButtonText = yesButtonText,
         NoButtonText = noButtonText
     };
     await _popupNavigation.PushAsync(confirmationDialog);
     var result = await confirmationDialog.GetResult();
     await _popupNavigation.PopAllAsync();
     return (bool)result;
}

The _popupNavigation object is Rg.Plugins.Popup.Services.PopupNavigation.Instance.

Usage in View Model Layer

Here, I call the service to prompt the dialog to the end user and wait for the UI response to take a cascaded action.

C#
private ICommand _selectedProductCommand;
public ICommand SelectedProductCommand => _selectedProductCommand ?? 
    (_selectedProductCommand = new Command<Product>(async (selectedProduct) =>
{
   var confirmed = await _dialogService.ShowConfirmationDialogAsync
                   ($"Are you sure you want to delete Item: {selectedProduct.Name} ?");
   if (!confirmed)
       return;

   ProductList.Remove(selectedProduct);
}));

Code Structure

Image 3

Design

Image 4

Dialog Base Definition

DialogBase.xaml contains a ContentView as actions place holder. The actions will be passed from the concrete dialog definition.

XML
<popup:PopupPage xmlns:popup="clr-namespace:Rg.Plugins.Popup.Pages;
assembly=Rg.Plugins.Popup" xmlns="http://xamarin.com/schemas/2014/forms" 
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
        x:Class="XamF.Controls.CustomDialogs.Base.DialogBase"
        x:Name="dialogBasePage">
    <ContentPage.Content>
        <Frame WidthRequest="250" BackgroundColor="White" 
         HorizontalOptions="Center" VerticalOptions="Center" 
         Padding="30" CornerRadius="0">
            <Grid RowSpacing="20">
                <Grid.RowDefinitions>
                    <RowDefinition Height="*"></RowDefinition>
                    <RowDefinition Height="Auto"></RowDefinition>
                </Grid.RowDefinitions>
                <Label x:Name="lblMessage" 
                Grid.Row="0" VerticalTextAlignment="Center" 
                 HorizontalTextAlignment="Center" HorizontalOptions="Center" 
                 VerticalOptions="StartAndExpand" FontSize="Medium"></Label>
                <ContentView Grid.Row="1" 
                 Content="{Binding Source={x:Reference dialogBasePage}, 
                 Path=ActionsPlaceHolder}"  
                 HorizontalOptions="Center" VerticalOptions="End">
                </ContentView>
            </Grid>
        </Frame>
    </ContentPage.Content>
</popup:PopupPage>

DialogBase.cs contains a protected TaskCompletionSource so that the concrete dialog can use it to SetResult.

C#
public abstract partial class DialogBase : PopupPage
{
    #region Variables
    protected Dictionary<DialogAction, Task> DialogActions;
    protected Action OnApearing;
    protected TaskCompletionSource<object> Proccess;
    //protected ILocator _locator;
    protected IPopupNavigation _popupNavigation;
    #endregion Variables
    #region Properties
    protected string _message;
    protected string Message { get => _message; set => _message = value; }
    #endregion Properties
    public DialogBase()
    {
        InitializeComponent();
        //_locator = App.Locator;
        _popupNavigation = PopupNavigation.Instance;
        this.CloseWhenBackgroundIsClicked = false;
    }
    #region Bindable Properties
    public static readonly BindableProperty ActionsPlaceHolderProperty
        = BindableProperty.Create
          (nameof(ActionsPlaceHolder), typeof(View), typeof(DialogBase));
    public View ActionsPlaceHolder
    {
        get { return (View)GetValue(ActionsPlaceHolderProperty); }
        set { SetValue(ActionsPlaceHolderProperty, value); }
    }
    #endregion Bindable Properties
    protected override void OnAppearing()
    {
        base.OnAppearing();
        this.lblMessage.Text = _message;
        OnApearing?.Invoke();
        Proccess = new TaskCompletionSource<object>();
    }
    public virtual Task<object> GetResult()
    {
        return Proccess.Task;
    }
}

Confirmation Dialog Definition

XAML:

XML
<dialogBase:DialogBase
    xmlns:dialogBase="clr-namespace:XamF.Controls.CustomDialogs.Base" 
    xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamF.Controls.CustomDialogs.ConfirmationDialog">
    <dialogBase:DialogBase.ActionsPlaceHolder>
        <StackLayout Orientation="Horizontal" Grid.Row="1">
            <Button x:Name="btnYes" Text="Yes" WidthRequest="80"></Button>
            <Button x:Name="btnNo" Text="No" WidthRequest="80"></Button>
        </StackLayout>
    </dialogBase:DialogBase.ActionsPlaceHolder>
</dialogBase:DialogBase>

Code behind:

C#
public partial class ConfirmationDialog : DialogBase
{
    public string YesButtonText { get; set; }
    public string NoButtonText { get; set; }
    public ConfirmationDialog(string message)
    {
        InitializeComponent();
        _message = message;
        OnApearing = () =>
        {
            this.btnNo.Text = NoButtonText;
            this.btnYes.Text = YesButtonText;
        };
        this.btnNo.Clicked += (sender, args) =>
        {
            Proccess.SetResult(false);
        };

        this.btnYes.Clicked += (sender, args) =>
        {
            Proccess.SetResult(true);
        };
    }
}

Conclusion

The objective is developing a reusable library that wraps a 3rd party UI package and calls it asynchronous task-based that can be handed out to consumers (view model).

History

  • 8th December, 2019: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)