Override Back Button in ViewModel

Oct 17, 2014 at 10:02 AM
Does Prism for WinRT support overriding or disabling the Windows Phone back button for individual pages? How is it done?
Oct 22, 2014 at 7:30 PM
Hi,

Maybe this example will help you. You need to download Windows 8.1 Store app samples and look at the sample called "App settings sample" and study the "Select Scenerio" No. 5)
Oct 24, 2014 at 7:22 AM
Edited Oct 24, 2014 at 8:31 AM
VcDeveloper wrote:
Hi,

Maybe this example will help you. You need to download Windows 8.1 Store app samples and look at the sample called "App settings sample" and study the "Select Scenerio" No. 5)
Hi.

Thank you for digging this out for me. This seems to be the "standard" way to override back button behavior. I was more looking for Prism "way of doing things". Is it so that Prism provides the navigation framework, but when I want to override the phone's back button I cannot to that in Prism but need to bypass it on page level?

I guess could simply prevent the back navigation canceling it in OnNavigatingFrom() and tie it to some condition but, as opposed to the NavigatedTo/From events, it isn't implemented in Prism VisualStateAwarePage and then not accessible in the ViewModel.

Either way, it means moving some of the logic from the ViewModel back to the View - along with the information it needs. Is this really intended?

-jgoe
Oct 31, 2014 at 4:25 AM
Hi

I'm not positive, but I don't think you will pass the Windows Phone evaluation, because I believe doing it that way will not be following the standard guidelines. It would be best to ask over in "Developing for Windows Phone"
Jan 5, 2015 at 9:29 PM
I have the same issue. On my mainpage (which by definition is the first page when the app opens) I have a drawer and when the app is open, i need to handle the Back Button differently (closing the App drawer rather than closing the app).

Only if the apps drawer is closed, the back button shall close the App
Jan 12, 2015 at 1:53 AM
I achieved this by creating a new interface, and handling it in the App.Xml.cs and view model.

The interface is used by the view models, allowing them to cancel navigation of the back button.
    public interface IRevertState
    {
        bool CanRevertState();
        void RevertState();
    }
The implementation for the view model.
    public class WelcomePageViewModel : ViewModel, IRevertState
    {
        public bool IsNewUser {get; set;}
        public bool IsSigningIn {get; set;}
        public bool CanRevertState()
        {
            // If the new user is true, then we can revert state back to a non-new user requiring sign-in.
            return this.IsNewUser;
        }

        /// <summary>
        /// Reverts the state.
        /// </summary>
        public void RevertState()
        {
            // Disable the new user mode.
            this.IsNewUser = false;
            this.IsSigningIn = false;
            this.ClearPasswords();
        }

        private void ClearPasswords()
        {
            // do stuff.....
        }
    }
Lastly, within your App.Xaml.cs you can handle the navigate back button being pressed.
        protected override void OnHardwareButtonsBackPressed(object sender, BackPressedEventArgs e)
        {
            var page = (Page)((Frame)Window.Current.Content).Content;
            if (page.DataContext is IRevertState)
            {
                var revertableState = (IRevertState)page.DataContext;
                if (revertableViewModel.CanRevert())
                {
                    revertableViewModel.RevertState();
                    e.Handled = true;
                    return;
                }
            }
            base.OnHardwareButtonsBackPressed(sender, e);
        }
Now anytime the back button is pressed, the app-wide method will handle it. You can easily add support for state cancellation to any view model, with zero code-behind by just allowing it to implement the IRevertState interface.
Marked as answer by jgoe on 1/14/2015 at 12:45 AM
Jan 13, 2015 at 1:46 PM
an excellent implementation, but my issue with it (using prism with a universal app) is when CanRevert is set to false the app just closes, it doesn't "block" the back button press.
I can feel we are getting close!,
Jan 13, 2015 at 8:06 PM
If your App.Xaml.cs is inheriting from MvvmAppBase then just mKe sure you properly override the OnHardwareButtonsBackPressed in your App.xaml.cs. It should catch and block the back button if you set the e.Handled to true based on CanRevert & Revert. I perform this on my start up page, and it stops the app from closing down.
Jan 13, 2015 at 8:07 PM
I want to point out that I am using this in a Universal App wired up with Prism as well.
Jan 13, 2015 at 9:50 PM
Sorry for coming back so late. I solved it similarly after long search and trying to get it done.
    public interface IBackNavigationAware
    {
        bool CanGoBack { get; }
    }
and in App.xaml.cs
            var currentFrame = Window.Current.Content as Frame;
            var currentView = currentFrame.Content as IView;
            var backNavigationAwareViewModel = currentView.DataContext as IBackNavigationAware;
            if(backNavigationAwareViewModel ==  null || backNavigationAwareViewModel.CanGoBack)
            {
                base.OnHardwareButtonsBackPressed(sender, e);
            }
I am still thinking if I should add an "OnBackNavigationCanceled" method on the interface to rise, when the back navigation failed and let the ViewModel handle the reason for the failure (show Popup/Flyout etc), since I can't handle the backnavigation anyway in the ViewModel by any other means
Jan 14, 2015 at 8:44 AM
Edited Jan 14, 2015 at 11:37 AM
Beautiful! Thank you.

About VcDeveloper's concern that it might violate Windows Phone app policies: have you been able to submit an app with the back button handled this way? This is definitely the cleaner solution and providing better user experience than, for instance, redirecting the user from the return page. But it might indeed be considered a violation of e.g. section 5.2.4 of the Technical Certification Requirements. What's your experience?

Thanks
-jgoe

Edit: Just realized that those requirements have been replaced with something more vague, which adds to the uncertainty.
Jan 14, 2015 at 10:30 AM
Edited Jan 14, 2015 at 10:30 AM
Just so i'm clear, implementing either of these two methods won't "swallow" the back button press - i.e if CanRevert = false the user can't just keep pressing back and the app remain on the same page?

The behaviour I am getting is if CanRevert = true the app closes, and if CanRevert = false the app navigates to the previous page.

The first behavior description is what I am aiming for, but either way these two methods are a step up from the default behavior, so I take my hat off to you!

Kris
Jan 14, 2015 at 11:42 AM
Edited Jan 14, 2015 at 11:43 AM
@Kris: Depends on what you put into the RevertState() method. That being empty and CanRevert=true effectively blocks back navigation - at least that's my observation and it matches the functionality I was looking for. Could something else you tried interfere with your navigation?
Jan 14, 2015 at 8:24 PM
Your method swallows the back button it looks like which is not what you want. You need the RevertState() method so that you can put the view model in to a state that allows the back button to exit the application after changing the state.

This is an example
    public class WelcomePageViewModel : ViewModel, IRevertState
    {
        public bool IsCreatingNewUser { get; set; }

        public bool CanRevertState()
        {
            return IsCreatingNewUser; // If we are creating a new user, then we can revert back to a Login state.
        }

        public void RevertState()
        {
            this.IsCreatingNewUser = false;
        }
    }
In this example, when the app starts it shows a Login state. If the user presses Create-User, I use the same view but change states to a IsCreatingNewUser state. If the back button is pressed, we check if we are creating a new user. If so, the RevertState() method is called and state is reverted back to Login (by setting IsCreatingNewUser to false).

If the back button is pressed again, CanRevertState() is false, so the back button is allowed to exit the application. There's no swallowing of the back button, we are ensuring the user can navigate back to the previous state and then exit the app. The RevertState() call allows you to set the state, so that the next back button press will allow CanRevertState() to return false and exit the app.
Jan 15, 2015 at 11:33 AM
Ah I get it now!

What I have currently is a login page which the user enters credentials into, clicks a login button and then is navigated to the apps main page.

Once on the main page, if the user hits the back button, I want to display a "Are you sure?" message with yes and no. Clicking yes logs the user out and returns to the login page, and clicking no just closes the dialog and returns to the main page.

I have all the code to do all of this, and the two pages exist as separate xaml pages and the navigation service is used to navigate between them.

However, no matter what I try the prism nav service always seems to just override any navigation requests and forces them through - so as it stands, when the user hits Back the message box is displayed for a few seconds and the navigation request back to the login page is just done anyway.

However a bit of reworking using your code, and having login/main exist on the same page as separate states might solve the problem.
Jan 15, 2015 at 11:41 PM
Edited Jan 15, 2015 at 11:42 PM
Can you paste your method that shows the alert dialog? It sounds like your using an async void method on an async dialog display, which allows the prism navigation to continue.
Jan 15, 2015 at 11:45 PM
You probably shouldn't ask the user "are you sure you want to exit?" when navigating out of the app. If it is at the last state/top-of-navigation-stack it should be allowed to just dump the user back out to their phone. Prompting them would be annoying. I'd recommend just preserving state and letting them leave. Only use this back button redirect to revert to a different state in the view.
Jan 26, 2015 at 12:15 PM
Hi sorry for the slow reply,

What I am looking to do is have the following:

LoginPage -> HubPage

When a user logs in, they are navigated to the hub page.
On the app bar of the HubPage is a "logout" button, and this displays an "are you sure?" message. The code to do this is as follows

private async void LogoutClick()
    {
        var messageDialog = new MessageDialog("Are you sure you want to logout?");

        messageDialog.Commands.Add(new UICommand(
            "Yes",
            new UICommandInvokedHandler(this.CommandInvokedHandler)));
        messageDialog.Commands.Add(new UICommand(
            "No",
            new UICommandInvokedHandler(this.CommandInvokedHandler)));

        // Set the command that will be invoked by default
        messageDialog.DefaultCommandIndex = 0;

        // Set the command to be invoked when escape is pressed
        messageDialog.CancelCommandIndex = 1;

        // Show the message dialog
        await messageDialog.ShowAsync();
    }
and the CommandInvokedHandler looks like this

private async void CommandInvokedHandler(IUICommand command)
    {
        // Display message showing the label of the command that was invoked
        if (command.Label == "Yes")
        {
            //user selected yes
            _eventAggregator.GetEvent<IsBusyEvent>().Publish(true);
            await _dataService.LogoutAsync(_userProfileService.DeviceID);
            await _notificationRepository.LogoutAsync();
            //clear device cache so it's fresh for next login
            _userProfileService.DeleteSettings();
            //navigate back to login page and clear navigation backstack
            _eventAggregator.GetEvent<IsBusyEvent>().Publish(false);
            if (_navigationService.CanGoBack())
            {
                _navigationService.GoBack();
            }
        }
    }
This all works fine with the appbar button.

However, I would like to do the same thing when the user presses the Back button while on the HubPage - so they get an "are you sure?" message before the navigation back to the login page is allowed to continue.

Through trial and error using code from different places, the best I have been able to do is to get the message to display, but in the background the navigation back is carried out anyway. I cant make it wait for the user to answer the message first. With everything I have tried the e.handled = true just gets ignored and prism does it's own thing.

I hope this makes sense. After @ScionWest mentioned using async void (which I am) that's triggered alarm bells that suggest I should be using Task instead.

Any help would be great. This is driving me nuts

Kris