Forum Xamarin.Forms

Return value from PushModalAsync

JeremyHerbisonJeremyHerbison CAMember ✭✭
edited March 2015 in Xamarin.Forms

I have a few pages that I'd like to call like functions. As in, I'd like to show the page modally, then do something with a result.

Because awaiting PushModalAsync doesn't actually block until popped, i'm using messagecenter to subscript for the result. It seems kinda clunky. Is there a more elegant way to do this?

        // what I think I have to do:
        async Task TakePicture()
        {
            MessagingCenter.Subscribe<CameraPageViewModel, ImageSource>(this, "CameraImage", async (sender, imageSource) =>
            {
                ImageSource = imageSource;
            });
            await ViewModelNavigation.PushModalAsync(new CameraPageViewModel()); // This returns right away
        }

        // what I wish I could do:
        async Task TakePicture()
        {
            var cameraPage = new CameraPageViewModel();
            await ViewModelNavigation.PushModalAsync(cameraPage); // In my imagination, this blocks until Popped
            ImageSource = cameraPage.Image;
        }

Posts

  • JeremyHerbisonJeremyHerbison CAMember ✭✭

    That's much simpler, thanks :smile:

  • EdHubbellEdHubbell USMember ✭✭

    Not trying to nitpick, @adamkemp - Especially 18 month old threads with code typed right into the browser (which is a game I can't play).

    But I think it should be

    private Task<ImageSource> TakePicture()
    

    (no async)

    With the async, I get a 'Since this is async method, the return expression must be of type ImageSource'. Other than that, this method worked great for me. Left this note here in case it helps someone else.

    I swear I use more of your forum responses than all others combined. So thanks.

  • adamkempadamkemp USInsider, Developer Group Leader mod

    You're right. I edited the comment to avoid confusing more people. :)

  • Hi,

    I'm trying to do something similar, using SignaturePad within a modal page, I would like to return the image/imagesource back when the modal page is popped.

    I have a xaml page like this:

    <SignaturePadView x:Name="PadView"
                Grid.Row="0" 
                HeightRequest="245" 
                WidthRequest="240"
                BackgroundColor="White"
                CaptionText="{localization:Translate SignaturePad_Caption}"
                CaptionTextColor="Black"
                ClearText="{localization:Translate SignaturePad_Clear}"
                ClearTextColor="Red"
                PromptText="{localization:Translate SignaturePad_Prompt}"
                PromptTextColor="Silver"
                SignatureLineColor="Aqua"
                StrokeColor="Black"
                StrokeWidth="3">
    </SignaturePadView>
    
      <Grid Grid.Row="1" Padding="20">
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="*"/>
          <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
    
        <Button x:Name="SaveButton"
                Text="{localization:Translate SaveButton}"
                Grid.Column="0"
                StyleClass="Success"
                Clicked="SaveButton_OnClicked"/>
    
        <Button x:Name="AbortButton"
                Text="{localization:Translate AbortButton}"
                Grid.Column="1"
                StyleClass="Warning"
                Clicked="AbortButton_OnClicked"/>
      </Grid>
    

    I tried to replicate the @adamkemp's code but I have not idea of what I'm doing

    Can someone, please, give me a more completely example?

    thanks

  • EdHubbellEdHubbell USMember ✭✭

    Well, I can share what I'm using this method with. Not a signature pad, but maybe you can adapt it. I've got a page that has nothing but a list of Cart objects in it. Looks like this:

    <?xml version="1.0" encoding="UTF-8"?>
    <ContentPage 
        xmlns="http://xamarin.com/schemas/2014/forms" 
        xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
        x:Class="SomeApp.SelectCartPage"
        Title="Select Cart"
        >
    
        <ContentPage.Padding>
            <OnPlatform x:TypeArguments="Thickness">
                <OnPlatform.iOS>0, 20, 0, 0</OnPlatform.iOS>
                <OnPlatform.Android>0, 0, 0, 0</OnPlatform.Android>
                <OnPlatform.WinPhone>0, 0, 0, 0</OnPlatform.WinPhone>
            </OnPlatform>
        </ContentPage.Padding>
    
        <ContentPage.Content>
    
            <ListView
                x:Name="listView"
                ItemTapped="Handle_ItemTapped"
                ItemsSource="{Binding .}"
                >
    
                <ListView.ItemTemplate>
    
                    <DataTemplate>
    
                        <TextCell 
                            Text="{Binding vin, StringFormat='VIN {0}'}" 
                            Detail="{Binding business_name, StringFormat='{0}'}"
                            TextColor="{DynamicResource Blue}"
                            DetailColor="Gray"
                        />
    
                    </DataTemplate>
                </ListView.ItemTemplate>
    
            </ListView> 
    
        </ContentPage.Content>
    </ContentPage>
    

    Code behind looks like this

    using System;
    using System.Collections.Generic;
    using System.Threading.Tasks;
    using Xamarin.Forms;
    
    namespace SomeApp
    {
        public partial class SelectCartPage : ContentPage
        {
    
            public List<Cart> _carts;
    
            private TaskCompletionSource<Cart> _taskCompletionSource;
    
            public SelectCartPage(List<Cart> carts)
            {
    
                _carts = carts;
                BindingContext = this._carts;
    
                InitializeComponent();
    
            }
    
            void Handle_ItemTapped(object sender, Xamarin.Forms.ItemTappedEventArgs e)
            {
    
                if (_taskCompletionSource != null)
                {
                    _taskCompletionSource.SetResult((Cart)e.Item);
                    _taskCompletionSource = null;
                }
    
            }
    
            private Task<Cart> GetCart()
            {
                _taskCompletionSource = new TaskCompletionSource<Cart>();
                return _taskCompletionSource.Task;
            }
    
            public static async Task<Cart> Cart(INavigation navigation, List<Cart> carts)
            {
                var viewModel = new SelectCartPage(carts);
                await navigation.PushModalAsync(viewModel);
                var cart = await viewModel.GetCart();
                await navigation.PopModalAsync();
                return cart;
            }
    
        }
    
    
    }
    

    When I call it, all I want back is a cart object back. So calling it looks like this:

    getCartGestureRecognizer.Tapped += async (sender, e) =>
                    {
                        cart = await SelectCartPage.Cart(Navigation, _carts);
                        lblVINSelect.Text = cart.vin;
                    };
    

    Hope that helps. The syntax is a bit convoluted, calling a public static method inside the page that fires off a version of itself, blah blah... But doesn't involve message handlers or dixie cups with strings. Has worked for me so far.

  • ShimmyWeitzhandlerShimmyWeitzhandler USMember ✭✭✭
    edited July 2017

    @BrianLagunas

    I'm curious how you'd implement this with Prism.
    I want to implement a Task<Credentials> GetCredentials() method that when called, replaces the main view (or blocks with a modal) that returns when the user clicks OK or cancel (returning null) on the login page.

  • FreddyKrugerFreddyKruger Member ✭✭

    I know this is an old thread but I was looking for a similar solution. I have tried this and it works great. I have one question though. If the user presses the backbutton on his device the page that was opened by PushModalAsyc gets popped. But the TakePicture task does not return. It keeps waiting for an answer. Isn't that a memory leak? What happens with that task if the answer never comes because the request was "cancelled"?

  • NMackayNMackay GBInsider, University admin

    @FreddyKruger said:
    I know this is an old thread but I was looking for a similar solution. I have tried this and it works great. I have one question though. If the user presses the backbutton on his device the page that was opened by PushModalAsyc gets popped. But the TakePicture task does not return. It keeps waiting for an answer. Isn't that a memory leak? What happens with that task if the answer never comes because the request was "cancelled"?

    This approach can easily cause a memory leak so run such solutions through a profiler to see if the modal is ever released.

  • FreddyKrugerFreddyKruger Member ✭✭

    @NMackay said:

    @FreddyKruger said:
    I know this is an old thread but I was looking for a similar solution. I have tried this and it works great. I have one question though. If the user presses the backbutton on his device the page that was opened by PushModalAsyc gets popped. But the TakePicture task does not return. It keeps waiting for an answer. Isn't that a memory leak? What happens with that task if the answer never comes because the request was "cancelled"?

    This approach can easily cause a memory leak so run such solutions through a profiler to see if the modal is ever released.

    Is there a better alternative to this solution? Because whatever solution I seem to read of are all vulnerable to memory leaks. All because there is no simple way of knowing that the pushed page has been closed by the user.

  • NMackayNMackay GBInsider, University admin

    I usually use Prism and hook into the navigation event back, that doesn't always match all scenarios but it's a nicer approach without the leaks.

  • JohnHardmanJohnHardman GBUniversity admin

    @FreddyKruger said:
    Is there a better alternative to this solution? Because whatever solution I seem to read of are all vulnerable to memory leaks. All because there is no simple way of knowing that the pushed page has been closed by the user.

    Can you use PushAsync instead? If so, you can use the Popped event of NavigationPage to handle cleanup of any resources, including event handlers, subscriptions etc. I use a common base class for my ContentPages, and have a virtual OnPopped method in there that each subclass can override. I have OnPopped called from the Popped event handler.

  • FreddyKrugerFreddyKruger Member ✭✭

    @NMackay said:
    I usually use Prism and hook into the navigation event back, that doesn't always match all scenarios but it's a nicer approach without the leaks.

    @JohnHardman said:

    @FreddyKruger said:
    Is there a better alternative to this solution? Because whatever solution I seem to read of are all vulnerable to memory leaks. All because there is no simple way of knowing that the pushed page has been closed by the user.

    Can you use PushAsync instead? If so, you can use the Popped event of NavigationPage to handle cleanup of any resources, including event handlers, subscriptions etc. I use a common base class for my ContentPages, and have a virtual OnPopped method in there that each subclass can override. I have OnPopped called from the Popped event handler.

    I can use PushAsync if I must. I have seen NMackay's example over here and I will give that a try. I understand that PushModalAsync won't run the Popped event?

  • JohnHardmanJohnHardman GBUniversity admin

    I understand that PushModalAsync won't run the Popped event?

    Not the last time I checked.

  • NMackayNMackay GBInsider, University admin
    edited November 2019

    This bug suggests the Popped event is raised by modals now although it never used to be like John says.

    Watch out for this bug is your using iOS and Forms 4.3.0
    https://github.com/xamarin/Xamarin.Forms/issues/7878

  • JohnHardmanJohnHardman GBUniversity admin

    @NMackay said:
    This bug suggests the Popped event is raised by modals now although it never used to be like John says.

    Watch out for this bug is your using iOS and Forms 4.3.0
    https://github.com/xamarin/Xamarin.Forms/issues/7878

    I'm running 4.3.0.947036. Just did a quick test on UWP, and Popped was not fired when popping a modal page.

  • FreddyKrugerFreddyKruger Member ✭✭

    @JohnHardman said:

    I understand that PushModalAsync won't run the Popped event?

    Not the last time I checked.

    @NMackay said:
    This bug suggests the Popped event is raised by modals now although it never used to be like John says.

    Watch out for this bug is your using iOS and Forms 4.3.0
    https://github.com/xamarin/Xamarin.Forms/issues/7878

    I checked and PushModalAsync did not raise the Popped event.
    I also found that PopToRootAsync does not raise Popped events (for non-modal pushes) and that is a little strange to me because I would have expected that the pages would raise Popped events from top to bottom for each page. Any reason for that? Because this would mean I need to pop the pages "manually" until I hit root.

  • JohnHardmanJohnHardman GBUniversity admin
    edited November 2019

    @FreddyKruger said:
    I also found that PopToRootAsync does not raise Popped events (for non-modal pushes) and that is a little strange to me because I would have expected that the pages would raise Popped events from top to bottom for each page. Any reason for that? Because this would mean I need to pop the pages "manually" until I hit root.

    PopToRootAsync raises PoppedToRoot. You can handle that to call cleanup code for all of the popped pages. This is my handler:

            private void OnPoppedToRoot(object sender, NavigationEventArgs e)
            {
                if (e is PoppedToRootEventArgs poppedToRootEventArgs)
                {
                    foreach (Xamarin.Forms.Page page in poppedToRootEventArgs.PoppedPages)
                    {
                        // call the page's cleanup code here. I check whether the page implements a cleanup interface of my own making and then call via that
                    }
                }
            }
    
  • FreddyKrugerFreddyKruger Member ✭✭

    @JohnHardman said:

    @FreddyKruger said:
    I also found that PopToRootAsync does not raise Popped events (for non-modal pushes) and that is a little strange to me because I would have expected that the pages would raise Popped events from top to bottom for each page. Any reason for that? Because this would mean I need to pop the pages "manually" until I hit root.

    PopToRootAsync raises PoppedToRoot. You can handle that to call cleanup code for all of the popped pages. This is my handler:

            private void OnPoppedToRoot(object sender, NavigationEventArgs e)
            {
                if (e is PoppedToRootEventArgs poppedToRootEventArgs)
                {
                    foreach (Xamarin.Forms.Page page in poppedToRootEventArgs.PoppedPages)
                    {
                        // call the page's cleanup code here. I check whether the page implements a cleanup interface of my own making and then call via that
                    }
                }
            }
    

    Thank you so much for the code. I'm learning by the minute thanks to you and @NMackay.

    I see that poppedToRootEventArgs.PoppedPages contains the pages in an order from bottom to top. That is confirmed by the Microsoft docs. So if I loop through the PoppedPages to clean them up the most upper page in the stack would be cleaned last. I think i'll revert the loop to avoid potential leaks with events created between the pages that are being popped.

  • NMackayNMackay GBInsider, University admin
    edited November 2019

    Prism does this automatically if your page/PartialView/viewmodel inherits IDestructible. My suggestion :) That link above was to work with MVVM Light but as John suggests, will work well with a non framework approach too.

    Lots causes leaks, a few obvious ones are:

    • Unhooked event handlers
    • Pubsub messagecenter event handlers not been unregistered
    • Behaviours not been cleared from controls (not an obvious one)
    • ImageSources not been set to null....well this should garbage collect eventually but always issues with not cleaning image sources.
    • Binding to singleton IoC registrations
    • etc
      etc
  • JohnHardmanJohnHardman GBUniversity admin

    @FreddyKruger said:
    I see that poppedToRootEventArgs.PoppedPages contains the pages in an order from bottom to top. That is confirmed by the Microsoft docs. So if I loop through the PoppedPages to clean them up the most upper page in the stack would be cleaned last. I think i'll revert the loop to avoid potential leaks with events created between the pages that are being popped.

    That's fair, although I would recommend decoupling your pages so that there are no dependencies between them to worry about.

Sign In or Register to comment.