Search code examples
c#windowsmvvmwindows-phonewindows-phone-8.1

navigation to a new page removes backstack


I have a strange recurring problem. Sometimes it goes away, other times it comes back. I can't pinpoint at all the issue, all my breakpoints seem to be hit in expected order.

When I navigate to a new page, my backstack keeps getting deleted, so pressing back just backgrounds the app. Obviously this is a problem.

I think it may be a result of my more complex page and viewmodel structures. I created a new class for all the NavigationHelper stuff for Pages enforcing that all my Pages subclass from the new class. I enforce that all my Pages attach themselves to a base PageViewModel class to resolve the communication between the two (I had a better way but Xaml doesn't play well), and I navigate using a NavigationService, where I call CurrentFrame, which is a static method for return Windows.Current.Content as Frame.

Here are what I think are relevant code. Any ideas? Thanks a bunch in advance. I have no clue what's going on :/

I navigate forward using the Navigate method in NavigationService (not the other two lolol), but my back button doesn't go back properly.


public abstract class BaseViewModelPage : Page
{
    protected readonly NavigationHelper NavigationHelper;

    protected BaseViewModelPage()
    {
        NavigationHelper = new NavigationHelper(this);
        NavigationHelper.LoadState += navigationHelper_LoadState;
        NavigationHelper.SaveState += navigationHelper_SaveState;

        this.NavigationCacheMode = NavigationCacheMode.Required;
    }

    protected BasePageViewModel CurrentPageViewModel
    {
        get { return DataContext as BasePageViewModel; }
    }

    #region Navigation Registration

    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        NavigationHelper.OnNavigatedTo(e);
    }

    protected override void OnNavigatedFrom(NavigationEventArgs e)
    {
        NavigationHelper.OnNavigatedFrom(e);
    }

    protected virtual void LoadState(LoadStateEventArgs e)
    {
        if (CurrentPageViewModel != null)
        {
            CurrentPageViewModel.LoadState(e);
        }
    }

    protected virtual void SaveState(SaveStateEventArgs e)
    {
        if (CurrentPageViewModel != null)
        {
            CurrentPageViewModel.SaveState(e);
        }
    }

    private void navigationHelper_LoadState(object sender, LoadStateEventArgs e)
    {
        LoadState(e);
    }

    private void navigationHelper_SaveState(object sender, SaveStateEventArgs e)
    {
        SaveState(e);
    }

    #endregion
}

public abstract class BasePageViewModel : ViewModelBase
{
    private bool _isLoading = false;
    public bool IsLoading
    {
        get
        {
            return _isLoading;
        }

        set
        {
            if (_isLoading == value)
            {
                return;
            }

            _isLoading = value;
            RaisePropertyChanged();
        }
    }

    public virtual void LoadState(LoadStateEventArgs e)
    {

    }

    public virtual void SaveState(SaveStateEventArgs e)
    {

    }
}

public class NavigationService : INavigationService
{
    public static readonly Dictionary<Type, Type> PageDictionary;

    static NavigationService()
    {
        PageDictionary = new Dictionary<Type, Type>();
        PageDictionary.Add(typeof(LogInPageViewModel), typeof(LogInPage));
        PageDictionary.Add(typeof(RegisterUserPageViewModel), typeof(RegisterUserPage));
    }

    public bool Navigate(Type pageViewModelType, Object parameter = null)
    {
        if (PageDictionary.ContainsKey(pageViewModelType))
        {
            if (parameter != null)
            {
                return App.CurrentFrame.Navigate(PageDictionary[pageViewModelType], parameter);
            }
            else
            {
                return App.CurrentFrame.Navigate(PageDictionary[pageViewModelType]);
            }
        }

        return false;
    }

    public bool GoBack()
    {
        if (CanGoBack())
        {
            App.CurrentFrame.GoBack();
        }

        return false;
    }

    public bool CanGoBack()
    {
        return App.CurrentFrame.CanGoBack;
    }

    public bool NavigateAndRemoveSelf(Type pageViewModelType, object parameter = null)
    {
        if (Navigate(pageViewModelType, parameter))
        {
            if (App.CurrentFrame.CanGoBack)
            {
                App.CurrentFrame.BackStack.RemoveAt(App.CurrentFrame.BackStackDepth - 1);
                return true;
            }
        }

        return false;
    }

    public bool NavigateAndRemoveAll(Type pageViewModelType, object parameter = null)
    {

        if (Navigate(pageViewModelType, parameter))
        {
            while (App.CurrentFrame.CanGoBack)
            {
                App.CurrentFrame.BackStack.RemoveAt(App.CurrentFrame.BackStackDepth - 1);
            }

            return true;
        }

        return false;
    }
}

Update [solved]:

The error is caused by using a Universal App Class Library.

I wanted to separate the NavigationHelper.cs class (generated by default in WP8 apps) into a library. so that I could unit test the VM directly (I could not reference the WP8 app with the Unit Test project). Thus, I placed the NavigationHelper.cs class, plus all my relevant code above, in a new Universal App Class Library.

The NavigationHelper class relies on two things, a WINDOWS_PHONE_APP macro in the BUILD, which affects this specific part in the NavigationHelper class, the HardwareButton BackPressed listener.

#if WINDOWS_PHONE_APP
            Windows.Phone.UI.Input.HardwareButtons.BackPressed += HardwareButtons_BackPressed;
#else

and a second reliance on the Windows.Phone assembly. The assembly exists in a WP8 app, but not for a Universal App Class Library. This means that even if I add the WINDOWS_PHONE_APP macro to the library, the app will not compile. You cannot use the NavigationHelper generated by Windows Phone 8/8.1 projects inside a Universal App Class Library. I will try to raise this issue. Thanks!


Solution

  • Update [solved]:

    The error is caused by using a Universal App Class Library.

    I wanted to separate the NavigationHelper.cs class (generated by default in WP8 apps) into a library. so that I could unit test the VM directly (I could not reference the WP8 app with the Unit Test project). Thus, I placed the NavigationHelper.cs class, plus all my relevant code above, in a new Universal App Class Library.

    The NavigationHelper class relies on two things, a WINDOWS_PHONE_APP macro in the BUILD, which affects this specific part in the NavigationHelper class, the HardwareButton BackPressed listener.

    #if WINDOWS_PHONE_APP
                Windows.Phone.UI.Input.HardwareButtons.BackPressed += HardwareButtons_BackPressed;
    #else
        ...
    #endif
    

    Because the MACRO wasn't defined, the back button wouldn't actually go back.

    A second problem was the missing Windows.Phone assembly. The assembly exists in a WP8 app, but not for a Universal App Class Library. This means that even if I add a WINDOWS_PHONE_APP macro to the library, the app will not compile. You cannot use the NavigationHelper generated by Windows Phone 8/8.1 projects inside a Universal App Class Library. I will try to raise this issue. Thanks!