locked
Bug in SuspensionManager.SaveAsync() or Frame.GetNavigationState()

    Question

  • I'm trying to implement proper suspend behavior in our app.  I'm using the updated SuspensionManager class provided in the release preview builds of Visual Studio.  When you create a new Metro style application using the Visual Studio templates, it puts this code in OnSuspend

            private async void OnSuspending(object sender, SuspendingEventArgs e)
            {
                var deferral = e.SuspendingOperation.GetDeferral();
                await SuspensionManager.SaveAsync();
                deferral.Complete();
            }

    SuspensionManager.SaveAsync() eventually calls the private method below in SuspensionManager.

            private static void SaveFrameNavigationState(Frame frame)
            {
                var frameState = SessionStateForFrame(frame);
                frameState["Navigation"] = frame.GetNavigationState();
            }
    

    The problem is that Frame.GetNavigationState() triggers a OnNavigatedFrom event on the page that is currently displayed.  That means that OnSuspend, the current page is told that it was navigated away from and so it should clean up any state, unsubscribe from static events, etc.  The problem is that the page isn't actually unloaded.  It's still the current page.  If your app is resumed without being terminated, the same partially "disposed" page is presented to the user.

    It seems like triggering an OnNavigatedFrom in Frame.GetNavigationState would be completely unexpected.

    I put together a simple sample app that illustrates the bug.  It's the vanila Metro Grouped Items app template plus the code below.  If you start the app, sharing on the home page works.  After the app is suspended then resumed, sharing doesn't work.

            protected override void OnNavigatedTo(NavigationEventArgs e)
            {
                base.OnNavigatedTo(e);
                DataTransferManager.GetForCurrentView().DataRequested += OnShareRequested;
            }
    
            protected override void OnNavigatedFrom(NavigationEventArgs e)
            {
                base.OnNavigatedFrom(e);
                DataTransferManager.GetForCurrentView().DataRequested -= OnShareRequested;
            }

    Monday, June 04, 2012 6:45 AM

Answers

  • fyi: OnNavigatingFrom appears to only get called when truely navigating from a page. Therefore, this may be the most appropriate spot for teardown code (vs. OnNavigatedFrom).

    However, one gotcha is that OnNavigatingFrom is called before OnNavigatedFrom (which is called before SaveState) so if you need those disposed objects in SaveState, you need to postpone your teardown.

    Not sure why navigation+suspend/resume has to be so messy. Might be a good opportunity for an open source framework. ;)

    Thursday, October 04, 2012 6:17 PM

All replies

  • Robert,

    I understand why that is done, it needs to be done in order to save the state of the current page, before it suspends.  This is the same behaviour as on the phone, but obviously the differences that on the phone when you get called to suspend you actually get suspended, whereas on Windows 8 is possible not to be suspended which causes the behaviour problems that you are describing.  So I can understand why that happens. 

    I guess the obvious solution is that they should call OnNavigatedFrom with a NavigationMode parameter of something like NavigationMode.Suspending, so then it is clear what is happening.  Currently just calls it with NavigationMode.Forward, so you can't know what is happening.

    I wonder if it would work ok if you dealt with the adding and removing of events in Loaded/Unloaded?

    ...Stefan

    Monday, June 04, 2012 9:03 PM
  • Hello Robert,

    I just want to clarify something with you before I investigate this further. Your app gets the "OnSuspending" event. Does it get a complementary "OnResuming" event?

    Typically "OnResuming" would call "SetNavigationState". This would trigger your "OnNavigatedTo" event to get triggered. The only way that I can think of that this would not happen is if you are never receiving the "OnResuming" event or you are not calling "SetNavigationState" from the "OnResuming" event.

    I hope this helps,

    James


    Windows Media SDK Technologies - Microsoft Developer Services - http://blogs.msdn.com/mediasdkstuff/

    Wednesday, June 06, 2012 10:29 PM
    Moderator
  • OnResuming is called on App, but calling OnNavigatedTo (via SetNavigationState?) in OnResuming is not the right thing to do either.  That signals the page was just loaded, so the page should be re-initialized.

    For example, imagine you have a page that plays videos.  In OnNavigatedTo you load the video and start playback.  If the user switches away the app is suspended.  When they switch back, if you called OnNavigatedTo again, you would fetch video metadata and reload the video.  That will make switching back to the app slow (and broken).

    The problem here is that OnNavigatedFrom is being misused.  It means - you are no longer the currently displayed page in the frame.  One of the things you do when that happens is save your page state, but you also dispose your resources used on that page.

    Same thing with OnNavigatedTo.  You load state and initialize resources.

    Either NavigationEventArgs.NavigationMode should have a different mode for Suspended or SaveState or there should be a separate Page.SaveState method.  As it stands now, there is no way to distinguish between your app suspending and navigating away from the page.


    Thursday, June 07, 2012 12:33 AM
  • Hello Robert,

    After reviewing the Windows source code the behavior that you describe is indeed by design. I will pass your design change suggestion on to the XAML devs. However, I don't think they are going to change the design this late in the game.

    Thanks,

    James


    Windows Media SDK Technologies - Microsoft Developer Services - http://blogs.msdn.com/mediasdkstuff/

    Tuesday, June 12, 2012 12:04 AM
    Moderator
  • Hi James,

    I appreciate you forwarding my feedback along.  That doesn't quite answer my question though.  As a reminder, my specific question is: Where do I subscribe and unsubscribe from the share charm?

    In other words, where does this go? :-)

    DataTransferManager.GetForCurrentView().DataRequested += OnShareRequested.

    Maybe it can't go in the page at all...

    Tuesday, June 12, 2012 2:23 AM
  • Unsubscribe = OnNavigatedFrom

    Subscribe = OnNavigatedTo

    The pattern is really clear. You need to assume that your application has been completely suspended and save off all of your navigation data, etc. You then need to reinitialize your application, navigation and page when the app resumes. I know that this appears to be additional overhead and may degrade the resume speed or your application but this is the way the pattern was designed.

    -James


    Windows Media SDK Technologies - Microsoft Developer Services - http://blogs.msdn.com/mediasdkstuff/

    Tuesday, June 12, 2012 11:27 PM
    Moderator
  • James,

    This is not how things work.  

    If you add this into GroupedItemsPage.xaml.cs (brand new GridApp)

    protected override void SaveState(Dictionary<string, object> pageState)
    {
                
        base.SaveState(pageState);
    }

    and put a breakpoint in there and in LoadState.  Then run the application, press the suspend button in visual studio and then press the resume button in visual studio (both on the Debug Location toolbar) then you will find that SaveState got called, but LoadsState never did after you resumed.  This is a huge problem for me, because and save state I remove some content to ensure that I don't have any memory leaks.  But if I don't get LoadState back again, then I don't know when to re-establish the content.  Now I can move my content removal to an Unloaded handler, but the events that Robert refers to really need to be established in Load/SaveState.

    What is the way to handle this?

    ...Stefan

    Thursday, June 21, 2012 3:01 AM
  • fyi: OnNavigatingFrom appears to only get called when truely navigating from a page. Therefore, this may be the most appropriate spot for teardown code (vs. OnNavigatedFrom).

    However, one gotcha is that OnNavigatingFrom is called before OnNavigatedFrom (which is called before SaveState) so if you need those disposed objects in SaveState, you need to postpone your teardown.

    Not sure why navigation+suspend/resume has to be so messy. Might be a good opportunity for an open source framework. ;)

    Thursday, October 04, 2012 6:17 PM
  • I meet a problem same as you say, this is big touble.
    Thursday, April 04, 2013 3:59 AM
  • I see that (at least with Windows 8.1) the e.SourcePageType matches page type when being suspended, otherwise it holds the page type of target page.

    This could be used I guess.


    Miha Markic [MVP C#] http://blog.rthand.com

    Tuesday, April 21, 2015 9:23 AM