none
[UWP] OnNavigatedTo can get called before OnNavigatedFrom of previous page RRS feed

  • Question

  • Hi everybody,

    I'm just about to develop a note-taking app that saves a note in the EditPage when pressing the BACK-button (thus, I save the data in the OnNavigatedFrom event). The MainPage then reload the data (in the OnNavigatedTo event) and displays the notes in a list. Nothing special so far, but now comes the weird part:

    My save operation contains some await operation, which might take some time (< 1s). And after I realized that the newly created note is sometimes not directly visible on the MainPage. After some investigation, I realized that the OnNavigatedTo event is sometimes called before the OnNavigatedFrom event. I didn't knew this was possible. Because from my Windows Phone 8 Silverlight background , this was not the case.

    Here some pseudo code:

    // MainPage
    protected override void OnNavigatedTo()
    {
        Debug.WriteLine("I'm first!");
    }
    
    // EditPage
    protectec override async void OnNavigatedFrom()
    {
        await Task(100); // some async code here
        Debug.WriteLine("I'm second :(");
    }

    Is this really the way it should behave? Or how do I (asynchronously) save data in a sub page, that will be loaded in its parent page after navigating back using the BACK-button?

    Thx in advance!

    Benjamin

    Thursday, December 3, 2015 11:44 PM

Answers

  • Hi Benjamin,

    Yes, I can reproduce it in my side, in my mind when we use the await and async method inside the OnNavigatedFrom method, when it executes the asynchronous method which might take some time (< 1s), it will open a new thread for it, so the main thread will execute the OnNavigatedTo method, after that it will execute the OnNavigatedFrom method. One workaround for this issue is to save the data in a static class, when the data changes, it will raise a change event as following:
    In the EditPage:

     public static class MyClass 
        {
            private static string APPdata_;      
            public static string AppData
            {
                get { return APPdata_; }
                set
                {
                    if (value != APPdata_)
                    {
                        APPdata_ = value;
                        PropertyChanged(null, null);
                    }
                }
            }
           public static event PropertyChangedEventHandler PropertyChanged;
        }
        public sealed partial class EditPage : Page
        {
            public EditPage()
            {
                this.InitializeComponent();
            }
    
            private void button_Click(object sender, RoutedEventArgs e)
            {
                this.Frame.Navigate(typeof(MainPage));
            }
            protected async override void OnNavigatingFrom(NavigatingCancelEventArgs e)
            {
                await Task.Delay(100);
                MyClass.AppData = "Hello";
            } 
        }

    In the MainPage:
      protected override void OnNavigatedTo(NavigationEventArgs e)
            {
                Debug.WriteLine("I'm first!");
                MyClass.PropertyChanged += MyClass_PropertyChanged;
            }
            private void MyClass_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
            {
                var t = MyClass.AppData;
            }

    Best Regards,
    Amy Peng

    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    Monday, December 7, 2015 5:13 AM
    Owner

All replies

  • Hi Benjamin,

    Yes, I can reproduce it in my side, in my mind when we use the await and async method inside the OnNavigatedFrom method, when it executes the asynchronous method which might take some time (< 1s), it will open a new thread for it, so the main thread will execute the OnNavigatedTo method, after that it will execute the OnNavigatedFrom method. One workaround for this issue is to save the data in a static class, when the data changes, it will raise a change event as following:
    In the EditPage:

     public static class MyClass 
        {
            private static string APPdata_;      
            public static string AppData
            {
                get { return APPdata_; }
                set
                {
                    if (value != APPdata_)
                    {
                        APPdata_ = value;
                        PropertyChanged(null, null);
                    }
                }
            }
           public static event PropertyChangedEventHandler PropertyChanged;
        }
        public sealed partial class EditPage : Page
        {
            public EditPage()
            {
                this.InitializeComponent();
            }
    
            private void button_Click(object sender, RoutedEventArgs e)
            {
                this.Frame.Navigate(typeof(MainPage));
            }
            protected async override void OnNavigatingFrom(NavigatingCancelEventArgs e)
            {
                await Task.Delay(100);
                MyClass.AppData = "Hello";
            } 
        }

    In the MainPage:
      protected override void OnNavigatedTo(NavigationEventArgs e)
            {
                Debug.WriteLine("I'm first!");
                MyClass.PropertyChanged += MyClass_PropertyChanged;
            }
            private void MyClass_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
            {
                var t = MyClass.AppData;
            }

    Best Regards,
    Amy Peng

    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    Monday, December 7, 2015 5:13 AM
    Owner
  • Hi Benjamin,

    as Amy's solution might work its more a workaround than getting grip on the root of the cause.

    The problem you are facing is typical pitfall of using "async void" which behaves very different from "async Task" and is often called "fire & forget".

    You find some great explanations and backgroudn infos in this 6 part video series over at Channel 9 - its more than worth to watch : https://channel9.msdn.com/Series/Three-Essential-Tips-for-Async

    As normally one should avoid using "async void" you cannot avoid it here as you are overriding a method and cannot change its signature ... but you can use a async coordination primitive. In your case an "async manual reset event" seems to makes sense - you find a implementation and great documentation over here : http://blogs.msdn.com/b/pfxteam/archive/2012/02/11/10266920.aspx. Stephen Toubs blog post are also a great source of knowledge and background information - as the blog post title suggest there are more parts on async coordination primitives and I would highly recommend to read them and put these classes into a "shared library" of your toolbox ... if you write more and more async & await code (apps) there more times you need them and they are easy to use and well explained.

    And to give you an idea how you can use this "async manual reset event" :

    1. define a "async manual reset event" property like PageSavesData e.g. in your app class and ensure that its set in the apps constructor so that is is initially in the state of "pass through"
    2. in your MainPage wait for it in the OnNavigatedTo : await (App.Current as App).PageSavesData.WaitAsync();
    3. in your EditPage (in the context of OnNavigatedFrom) before you do your first "await" reset the coordination primitive : (App.Current as App).PageSavesData.Reset(); ... which puts it in "blocking mode"
    4. in your EditPage (in the context of OnNavigatedFrom) after your last "await" set the coordination primitive : (App.Current as App).PageSavesData.Set(); ... which puts it in "pass through"

    So in the end your MainPage will be "blocked in async & await style" until the EditPage has saved its stuff.

    Hope this helps

    cheers gebco

    Saturday, January 23, 2016 2:51 PM