await for a PushModalAsync form to closed in xamarin forms

20,492

Solution 1

Use the Disappearing event on your modal page.

Example:

var modalPage = new ContentPage();
modalPage.Disappearing += (sender2, e2) =>
{
    System.Diagnostics.Debug.WriteLine("The modal page is dismissed, do something now");
};
await content.Navigation.PushModalAsync(modalPage);
System.Diagnostics.Debug.WriteLine("The modal page is now on screen, hit back button");

Or use a EventWaitHandle:

var waitHandle = new EventWaitHandle(false, EventResetMode.AutoReset);
var modalPage = new ContentPage();
modalPage.Disappearing += (sender2, e2) =>
{
    waitHandle.Set();
};
await content.Navigation.PushModalAsync(modalPage);
System.Diagnostics.Debug.WriteLine("The modal page is now on screen, hit back button");
await Task.Run(() => waitHandle.WaitOne());
System.Diagnostics.Debug.WriteLine("The modal page is dismissed, do something now");

Solution 2

A bit late on the answer here, but it might be best to listen to the Application's OnModalPagePopping event handler.

First, create the modal page. Give it a property to store the data you want to later retrieve:

public class MyModalPage : ContentPage
{
    public string Data { get; set; }

    public MyModalPage()
    {
        InitializeComponent();
        // ... set up the page ...
    }

    private async void PopThisPage()
    {
        // When you want to pop the page, just call this method
        // Perhaps you have a text view with x:Name="PhoneNumber", for example
        Data = PhoneNumber.Text; // store the "return value" before popping
        await MyProject.App.Current.MainPage.Navigation.PopModalAsync();
    }
}

In the parent page, you can create the modal page, and set up the event handler to listen for when the modal page pops:

public class MyPage : ContentPage
{
    MyModalPage _myModalPage;

    public MyPage()
    {
        InitializeComponent();
        // ... set up the page ...
    }

    private async void ShowModalPage()
    {
        // When you want to show the modal page, just call this method
        // add the event handler for to listen for the modal popping event:
        MyProject.App.Current.ModalPopping += HandleModalPopping;
        _myModalPage = new MyModalPage();
        await MyProject.App.Current.MainPage.Navigation.PushModalAsync(_myModalPage());
    }

    private void HandleModalPopping(object sender, ModalPoppingEventArgs e)
    {
        if (e.Modal == _myModalPage)
        {
            // now we can retrieve that phone number:
            var phoneNumber = _myModalPage.Data;
            _myModalPage = null;

            // remember to remove the event handler:
            MyProject.App.Current.ModalPopping -= HandleModalPopping;
        }
    }

}

This is better than using the OnDisappearing method, as others have already stated that can be called when the app is backgrounded, etc. And its behavior is not consistent across platforms.

There is also another event OnModalPopped, which is called after the modal is completely popped from the navigation stack. If using that, it should work similarly.

Solution 3

You can try to create an event, call when pop close.

public partial class AddSchool : ContentPage
{
    public delegate void PopupClosedDelegate();

    public event PopupClosedDelegate PopupClosed;

    public AddSchool()
    {
        InitializeComponent();
    }
    private async void Button_OK_Clicked(object sender, EventArgs e)
    {
        //doing some operations like entry to db etc and close page
        await Navigation.PopModalAsync();
        if (PopupClosed!=null)
        {
            PopupClosed();
        }
    }
    private async void cancelClicked(object sender, EventArgs e)
    {
        await Navigation.PopModalAsync();
        if (PopupClosed != null)
        {
            PopupClosed();
        }
    }
}

I put it on the button click event, maybe you can put on close or dispose event. Then here is implement

public partial class SelectSchool : ContentPage
{
    public SelectSchool()
    {
        InitializeComponent();
        #region toolbar
        ToolbarItem tbi = null;
        if (Device.OS == TargetPlatform.Android)
        {
            tbi = new ToolbarItem("+", "plus", async () =>
            {
                var target_page = new AddSchool();
                target_page.PopupClosed += () => { /*Do something here*/ };
                Navigation.PushModalAsync(target_page);
            }, 0, 0);
        }
        ToolbarItems.Add(tbi);
        #endregion

        this.Title = "Select School";

    }
}

Hope this help.

Solution 4

I create an extension method for this:

public static class DialogUtils
{
    public static async Task ShowPageAsDialog(this INavigation navigation, Page page)
    {
        int pagesOnStack = navigation.NavigationStack.Count + 1;
        var waitHandle = new EventWaitHandle(false, EventResetMode.AutoReset);
        page.Disappearing += (s, e) =>
        {
            if (navigation.NavigationStack.Count <= pagesOnStack)
                waitHandle.Set();
        };
        await navigation.PushAsync(page);
        await Task.Run(() => waitHandle.WaitOne());
    }
}

And I can use it:

private async void bShowDialogPage_Clicked(object sender, EventArgs e)
{
    var page = new DialogPage();
    await page.LoadData();
    await Navigation.ShowPageAsDialog(page);
    var result = page.PageResult;
}

It supports situations when dialog page show another page. I prefer NavigationStack instead ModalStack due to NavigationPage and BackButton.

Solution 5

Another way of accomplishing this is by calling an event from the page's OnDisapearing method, this event can then be subscribed by the navigation service which you create, and you can then use the "TaskCompletionSource" to wati until your page finishes its work and then complete the task. For more details about accomplishing this, you can check this blog post.

Here is the base page's implementation, every page in this demo app inherit this page:

public class BasePage<T> : ContentPage
{
    public event Action<T> PageDisapearing;
    protected T _navigationResut;

    public BasePage()
    {
    }

    protected override void OnDisappearing()
    {
        PageDisapearing?.Invoke(_navigationResut);
        if (PageDisapearing != null)
        {
            foreach (var @delegate in PageDisapearing.GetInvocationList())
            {
                PageDisapearing -= @delegate as Action<T>;
            }
        }
        base.OnDisappearing();
    }
}

Here is an overview of the navigation service you should use:

public async Task<T> NavigateToModal<T>(string modalName)
    {
        var source = new TaskCompletionSource<T>();
        if (modalName == nameof(NewItemPage))
        {
            var page = new NewItemPage();
            page.PageDisapearing += (result) =>
            {
                var res = (T)Convert.ChangeType(result, typeof(T));
                source.SetResult(res);
            };
            await App.Current.MainPage.Navigation.PushModalAsync(new NavigationPage(page));
        }
        return await source.Task;
    }

To call this page with the navigation service, you can use the following code:

 var item = await new SimpleNavigationService().NavigateToModal<Item>(nameof(NewItemPage));
        Items.Add(item);
Share:
20,492
Sebastian
Author by

Sebastian

working on .net technologies to create web , desktop and mobile apps

Updated on September 05, 2020

Comments

  • Sebastian
    Sebastian over 3 years

    I have a page and on clicking a plus button on toolbar i am calling a popup page

    from popup page user can add a new entry or cancel / close window without doing anything

    Everything is working fine and code is like this

        public partial class SelectSchool : ContentPage
        {
            public SelectSchool()
            {
                InitializeComponent();
                #region toolbar
                ToolbarItem tbi = null;
                if (Device.OS == TargetPlatform.Android)
                {
                    tbi = new ToolbarItem("+", "plus", async () =>
                    {
                        var target_page = new AddSchool(); 
                        Navigation.PushModalAsync(target_page);                                 
                    }, 0,0);
                }
                ToolbarItems.Add(tbi);
                #endregion
    
                this.Title = "Select School";
    
            }
        }
    

    And my popup page is like

         public partial class AddSchool : ContentPage
        {
            public AddSchool()
            {
                InitializeComponent();
            }
            private async void Button_OK_Clicked(object sender, EventArgs e)
            {
            //doing some operations like entry to db etc and close page
                 Navigation.PopModalAsync();
    
            }
            private void cancelClicked(object sender, EventArgs e)
            {
                Navigation.PopModalAsync();
            }
        }
    

    But now i want to wait for the Popup to get closed to do some additional coding and i tried below code

     if (Device.OS == TargetPlatform.Android)
                {
                    tbi = new ToolbarItem("+", "plus", async () =>
                    {
                        var target_page = new AddSchool(); 
                        await Navigation.PushModalAsync(target_page);  
                        //await till target_page is closed and once its closed call my next function here               
                    }, 0,0);
                }
    

    But await is not working . How can i await on this area till the popup getting closed ? Any idea??