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
Confirmation Dialog
Ok Dialog
IDialogService Implementation
The following is a code snippet from DialogService.cs file.
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.
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
Design
Dialog Base Definition
DialogBase.xaml contains a ContentView
as actions place holder. The actions will be passed from the concrete dialog definition.
<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
.
public abstract partial class DialogBase : PopupPage
{
#region Variables
protected Dictionary<DialogAction, Task> DialogActions;
protected Action OnApearing;
protected TaskCompletionSource<object> Proccess;
protected IPopupNavigation _popupNavigation;
#endregion Variables
#region Properties
protected string _message;
protected string Message { get => _message; set => _message = value; }
#endregion Properties
public DialogBase()
{
InitializeComponent();
_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:
<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:
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