Search code examples
asynchronouspopupmauimaui-community-toolkit

.NET MAUI PopUp CloseAsync() is not finishing


I'm trying to use PopUpService for displaying a Popup that on the one hand takes a parameter and returns one.

Unfortunately, upon returning the Popup does not seem to finish the async task and I don't know why. I assume some kind of CancellationToken is required.

Here the necessary code: In one ViewModel the PopUp is called:

public partial class SomeViewModel
{
    private readonly IPopupService _popupService;

    // ...

    public SomeViewModel(
        IPopupService popupService)
    {
        _popupService = popupService;
    }
    
    [RelayCommand]
    private async Task OnCallingPopUpAsync()
    {
        var result = await _popupService.ShowPopupAsync<PopupViewModel>(
                onPresenting: viewModel => viewModel.TransferValue(true)
            );
        if (result is bool boolResult)
            Debug.WriteLine("Success!!");
        else
            return;
    }
}

In the PopupViewModel the value is received. Furthermore, in the View on can just close the PopupView via pressing a button and calling the ClosePopupCommand:

public partial class PopupViewModel : Popup
{
    public ImagePickerViewModel() {}

    [RelayCommand]
    private async Task OnClosePopup()
    {
        await CloseAsync(true);
    }

    public void TransferValue(bool value)
    {
        Debug.WriteLine($"The transferred value is {value}");
    }
}

Nonetheless, the Popup does not close.

I have tried passing in a CancellationToken (similar how it is performed in the MS Docs, like the following:

[RelayCommand]
private async Task OnClosePopup()
{
    var cts = new CancellationTokenSource(TimeSpan.FromSeconds(1));
    await CloseAsync(true, token: cts.Token);
}

This, however, yielded an Exception: System.Threading.Tasks.TaskCanceledException: 'A task was canceled.'

How may I resolve this?


Solution

  • The Model-View-ViewModel (MVVM) pattern enforces a separation between three software layers — the XAML user interface, called the view, the underlying data, called the model, and an intermediary between the view and the model, called the viewmodel.

    So using CloseAsync() in ViewModel will not close the Popup.

    The easiest way is to close the Popup in code behind.

    Suppose we close the Popup when clicking a Button,

    private async void Button_Clicked(object sender, EventArgs e)
    {
        var cts = new CancellationTokenSource(TimeSpan.FromSeconds(1));
        await CloseAsync(true, token: cts.Token);
    }
    

    Or if you want to close the Popup in ViewModel, you may try passing the Popup through the CommandParameter using Relative Binding,

    <Button Text="click me" Command="{Binding ClosePopupCommand}" CommandParameter="{Binding Source={RelativeSource AncestorType={x:Type toolkit:Popup}}}"/>
    

    and resolve it and close it in the ViewModel,

        [RelayCommand]
        private void OnClosePopup(object o)
        {
            var cts = new CancellationTokenSource(TimeSpan.FromSeconds(1));
            var p = o as MyPopupPage;
    
            p.CloseAsync(false, token: cts.Token);
        }
    

    Update

    If relative binding not work, you may also try x:Reference Binding expression.

    Set the name of the Popup to "this",

    <toolkit:Popup 
    ...
                 x:Name="this">
    
    
    ...
    
    <Button Text="click me" Command="{Binding ClosePopupCommand}" CommandParameter="{Binding Source={x:Reference this}}"/>
    

    and pass popup using x:Reference Binding expression and close it in the ViewModel,

        [RelayCommand]
        private void OnClosePopup(object o)
        {
            var cts = new CancellationTokenSource(TimeSpan.FromSeconds(1));
            var p = o as MyPopupPage;
    
            p.CloseAsync(false, token: cts.Token);
        }
    

    Hope it helps!