Xamarin.forms: Can't Intercept Software Back Button

Created on 22 Feb 2018  ·  53Comments  ·  Source: xamarin/Xamarin.Forms

Description

Software back button doesn't use the same code paths as the hardware back button. This leads to unexpected behaviour on Android (and from what I've read, iOS).

Steps to Reproduce

  1. Override the "OnBackButtonPressed()" method on MasterDetailPage.
  2. Run application on Android and navigate to a detail page in the MasterDetailPage.
  3. Press the "Back" arrow on the command bar.

Expected Behavior

The "OnBackButtonPressed()" method should be invoked, same as when the hardware "back" button is pressed.

Actual Behavior

The method is not invoked.

Basic Information

  • Version with issue:
  • Last known good version: Unknown
  • IDE: Visual Studio 2017
  • Platform Target Frameworks:

    • Android: 8.0 Oreo

    • UWP: UWP 16299 (Works fine!)

  • Android Support Library Version: v7/v4
  • Nuget Packages:

NETStandard.Library {2.0.1} SQLite.Net.Standard
System.ServiceModel.Primitives {4.4.1} Adapt.Model.Helpdesk
NETStandard.Library {2.0.1} Adapt.Model.Helpdesk
System.Runtime.Serialization.Pri... {4.3.0} Adapt.Model.Helpdesk
System.ServiceModel.Http {4.4.1} Adapt.XivicClient.Standard
System.Reflection.TypeExtensions {4.4.0} Adapt.XivicClient.Standard
System.ServiceModel.Primitives {4.4.1} Adapt.XivicClient.Standard
NETStandard.Library {2.0.1} Adapt.XivicClient.Standard
System.Runtime.Serialization.Pri... {4.3.0} Adapt.XivicClient.Standard
System.ServiceModel.Primitives {4.4.1} Adapt.Model.Common.Standard
NETStandard.Library {2.0.1} Adapt.Model.Common.Standard
System.Runtime.Serialization.Pri... {4.3.0} Adapt.Model.Common.Standard
NETStandard.Library {2.0.1} Adapt.Presentation.Standard
Xamarin.Forms {2.5.0.280555} Adapt.Presentation.Standard
System.ServiceModel.Http {4.4.1} Adapt.XivicClient.Database.Standard
System.ServiceModel.Primitives {4.4.1} Adapt.XivicClient.Database.Standard
NETStandard.Library {2.0.1} Adapt.XivicClient.Database.Standard
System.Runtime.Serialization.Pri... {4.3.0} Adapt.XivicClient.Database.Standard
System.ServiceModel.Primitives {4.4.1} Adapt.Model.Whakatane
NETStandard.Library {2.0.1} Adapt.Model.Whakatane
System.Runtime.Serialization.Pri... {4.3.0} Adapt.Model.Whakatane
NETStandard.Library {2.0.1} Adapt.Business.Standard
Microsoft.CSharp {4.4.1} Adapt.Business.Standard
System.ServiceModel.Primitives {4.4.1} Adapt.Data.Generic.Standard
NETStandard.Library {2.0.1} Adapt.Data.Generic.Standard
System.Runtime.Serialization.Pri... {4.3.0} Adapt.Data.Generic.Standard
Xamarin.Forms.Maps {2.5.0.280555} Adapt.Presentation.XamarinForms
NETStandard.Library {2.0.1} Adapt.Presentation.XamarinForms
Syncfusion.Xamarin.SfDataGrid {15.4.0.20} Adapt.Presentation.XamarinForms
Xamarin.Forms {2.5.0.280555} Adapt.Presentation.XamarinForms
Xamarin.Android.Arch.Core.Common {1.0.0} Adapt.Presentation.Android
Xamarin.Android.Arch.Lifecycle.C... {1.0.1} Adapt.Presentation.Android
Xamarin.Android.Arch.Lifecycle.R... {1.0.0} Adapt.Presentation.Android
Xamarin.Android.Support.Animated... {26.1.0.1} Adapt.Presentation.Android
Xamarin.Android.Support.Annotations {26.1.0.1} Adapt.Presentation.Android
Xamarin.Android.Support.Compat {26.1.0.1} Adapt.Presentation.Android
Xamarin.Android.Support.Core.UI {26.1.0.1} Adapt.Presentation.Android
Xamarin.Android.Support.Core.Utils {26.1.0.1} Adapt.Presentation.Android
Xamarin.Android.Support.Design {26.1.0.1} Adapt.Presentation.Android
Xamarin.Android.Support.Fragment {26.1.0.1} Adapt.Presentation.Android
Xamarin.Android.Support.Media.Co... {26.1.0.1} Adapt.Presentation.Android
Xamarin.Android.Support.Transition {26.1.0.1} Adapt.Presentation.Android
Xamarin.Android.Support.v4 {26.1.0.1} Adapt.Presentation.Android
Xamarin.Android.Support.v7.AppCo... {26.1.0.1} Adapt.Presentation.Android
Xamarin.Android.Support.v7.CardView {26.1.0.1} Adapt.Presentation.Android
Xamarin.Android.Support.v7.Media... {26.1.0.1} Adapt.Presentation.Android
Xamarin.Android.Support.v7.Palette {26.1.0.1} Adapt.Presentation.Android
Xamarin.Android.Support.v7.Recyc... {26.1.0.1} Adapt.Presentation.Android
Xamarin.Android.Support.Vector.D... {26.1.0.1} Adapt.Presentation.Android
Xamarin.Forms {2.5.0.280555} Adapt.Presentation.Android
Xamarin.Forms {2.5.0.280555} Adapt.Presentation.iOS
Microsoft.NETCore.UniversalWindo... {6.0.7} Adapt.Presentation.UWP
Xamarin.Forms {2.5.0.280555} Adapt.Presentation.UWP
Esri.ArcGISRuntime.Xamarin.Android {100.2.0} Adapt.Presentation.Xivic.Android
Esri.ArcGISRuntime.Xamarin.Forms {100.2.0} Adapt.Presentation.Xivic.Android
Microsoft.NETCore.Platforms {2.0.1} Adapt.Presentation.Xivic.Android
NETStandard.Library {2.0.1} Adapt.Presentation.Xivic.Android
Syncfusion.Xamarin.Core {15.4.0.20} Adapt.Presentation.Xivic.Android
Syncfusion.Xamarin.GridCommon {15.4.0.20} Adapt.Presentation.Xivic.Android
Syncfusion.Xamarin.SfDataGrid {15.4.0.20} Adapt.Presentation.Xivic.Android
Syncfusion.Xamarin.SfDataGrid.An... {15.4.0.20} Adapt.Presentation.Xivic.Android
Syncfusion.Xamarin.SfNumericTextBox {15.4.0.20} Adapt.Presentation.Xivic.Android
Syncfusion.Xamarin.SfNumericText... {15.4.0.20} Adapt.Presentation.Xivic.Android
Xamarin.Android.Arch.Core.Common {1.0.0} Adapt.Presentation.Xivic.Android
Xamarin.Android.Arch.Lifecycle.C... {1.0.1} Adapt.Presentation.Xivic.Android
Xamarin.Android.Arch.Lifecycle.R... {1.0.0} Adapt.Presentation.Xivic.Android
Xamarin.Android.Support.Animated... {26.1.0.1} Adapt.Presentation.Xivic.Android
Xamarin.Android.Support.Annotations {26.1.0.1} Adapt.Presentation.Xivic.Android
Xamarin.Android.Support.Compat {26.1.0.1} Adapt.Presentation.Xivic.Android
Xamarin.Android.Support.Core.UI {26.1.0.1} Adapt.Presentation.Xivic.Android
Xamarin.Android.Support.Core.Utils {26.1.0.1} Adapt.Presentation.Xivic.Android
Xamarin.Android.Support.Design {26.1.0.1} Adapt.Presentation.Xivic.Android
Xamarin.Android.Support.Fragment {26.1.0.1} Adapt.Presentation.Xivic.Android
Xamarin.Android.Support.Media.Co... {26.1.0.1} Adapt.Presentation.Xivic.Android
Xamarin.Android.Support.Transition {26.1.0.1} Adapt.Presentation.Xivic.Android
Xamarin.Android.Support.v4 {26.1.0.1} Adapt.Presentation.Xivic.Android
Xamarin.Android.Support.v7.AppCo... {26.1.0.1} Adapt.Presentation.Xivic.Android
Xamarin.Android.Support.v7.CardView {26.1.0.1} Adapt.Presentation.Xivic.Android
Xamarin.Android.Support.v7.Media... {26.1.0.1} Adapt.Presentation.Xivic.Android
Xamarin.Android.Support.v7.Palette {26.1.0.1} Adapt.Presentation.Xivic.Android
Xamarin.Android.Support.v7.Recyc... {26.1.0.1} Adapt.Presentation.Xivic.Android
Xamarin.Android.Support.Vector.D... {26.1.0.1} Adapt.Presentation.Xivic.Android
Xamarin.Build.Download {0.4.7} Adapt.Presentation.Xivic.Android
Xamarin.Forms {2.5.0.280555} Adapt.Presentation.Xivic.Android
Xamarin.Forms.Maps {2.5.0.280555} Adapt.Presentation.Xivic.Android
Xamarin.GooglePlayServices.Base {60.1142.0} Adapt.Presentation.Xivic.Android
Xamarin.GooglePlayServices.Basement {60.1142.0} Adapt.Presentation.Xivic.Android
Xamarin.GooglePlayServices.Maps {60.1142.0} Adapt.Presentation.Xivic.Android
Xamarin.GooglePlayServices.Tasks {60.1142.0} Adapt.Presentation.Xivic.Android
Esri.ArcGISRuntime.Xamarin.Forms {100.2.0} Adapt.Presentation.Xivic.iOS
Esri.ArcGISRuntime.Xamarin.iOS {100.2.0} Adapt.Presentation.Xivic.iOS
Syncfusion.Xamarin.Core {15.4.0.20} Adapt.Presentation.Xivic.iOS
Syncfusion.Xamarin.GridCommon {15.4.0.20} Adapt.Presentation.Xivic.iOS
Syncfusion.Xamarin.SfDataGrid {15.4.0.20} Adapt.Presentation.Xivic.iOS
Syncfusion.Xamarin.SfNumericTextBox {15.4.0.20} Adapt.Presentation.Xivic.iOS
Xamarin.Forms {2.5.0.280555} Adapt.Presentation.Xivic.iOS
Xamarin.Forms.Maps {2.5.0.280555} Adapt.Presentation.Xivic.iOS
Esri.ArcGISRuntime.UWP {100.2.0} Adapt.Presentation.Xivic.UWP
Esri.ArcGISRuntime.Xamarin.Forms {100.2.0} Adapt.Presentation.Xivic.UWP
Microsoft.NETCore.UniversalWindo... {6.0.7} Adapt.Presentation.Xivic.UWP
Syncfusion.Xamarin.SfDataGrid {15.4.0.20} Adapt.Presentation.Xivic.UWP
System.Runtime.Serialization.Pri... {4.3.0} Adapt.Presentation.Xivic.UWP
Xamarin.Forms.Maps {2.5.0.280555} Adapt.Presentation.Xivic.UWP

  • Affected Devices: HTC U11

Reproduction Link

Will provide if asked.

backbutton high impact proposal-open enhancement ➕

Most helpful comment

Any way to increase priority?
I have had workarounds for years now, which sometimes didn't work anymore,...
My current workaround is also a really bad hack with disadvantages, would love to remove my custom navigation page renderers for good and fix some issues in the process.

All 53 comments

@samhouts Yeah that's pretty much exactly our issue. The aforementioned callback needs to be added to the codebase or the existing method needs to be enhanced to include events from the software back button.

@samhouts is this bug accepted yet? It's a big issue. What more information is needed?

I agree in the point that OnBackButtonPressed should handle both Hard- and Software-BackButtonss. Nonetheless, it isn't that hard to interecept both of them. I blogged on that topic here: https://msicc.net/xamarin-forms-the-mvvmlight-toolkit-and-i-taking-control-over-the-back-buttons/

@MSiccDev If we keep working around the problems presented by the framework, then it'll never get fixed and people will have to keep doing this themselves over and over again until the death of the technology.

@AceCoderLaura true on one side. On the other side, one has to move forward. Best thing is to work around but report the issue.

These workarounds break the hamburger menu on the root page...
-.-

@AceCoderLaura I am using it in one of my apps with a MD-Page without any problems...

@MSiccDev
Here's my hacky workaround:

public override bool OnOptionsItemSelected(IMenuItem item) 
{ 
            //If it's not the "home" button continue as usual 
            var androidHomeId = 16908332; 
            if (item.ItemId != Resource.Id.home && item.ItemId != androidHomeId) return base.OnOptionsItemSelected(item); 

            var navigation = App.MainMasterDetailPage.Detail.Navigation; 
            if (navigation.NavigationStack.Count > 1) 
            { 
                //We can go back, do the arrow functionality 
                this.OnBackPressed();
                return true;
            } 
            else 
            { 
                //We're at the root, do the hamburger functionality 
                return App.MainMasterDetailPage.IsPresented = true; 
            } 
}

@AceCoderLaura Are your OnOptionsItemSelected triggered after click on the nav-back button oO In my case, nothing happened...(Android)

Is there any progress for this issue? I have multiple nested navigationviews and it would be ultra hacky to patch this in my application. I aggree with @AceCoderLaura that this must be patched by xamarin.

Same problem. I think it's quite common to handle back event in enterprise app. It should be fixed asap.

Please fix this, it's been reported for close to a year, and it's ridiculous not to have it yet.

@ddobrev You're right this is a very useful feature, but unfortunately it hasn't even been clearly defined how it needs to work. Someone mentioned above it but it got no response:

Yeah that's pretty much exactly our issue. The aforementioned callback needs to be added to the codebase or the existing method needs to be enhanced to include events from the software back button.

Had this been clearly defined, I'd have tried to make a PR to implement it.

I dont know why, but in my case (on Android) was enough to add this to my MainActivity

        protected override void OnPostCreate(Bundle savedInstanceState)
        {
            var toolBar = FindViewById<global::Android.Support.V7.Widget.Toolbar>(Resource.Id.toolbar);
            SetSupportActionBar(toolBar);

            base.OnPostCreate(savedInstanceState);
        }

With this the OnBackButtonPressed is fired both even if i press the hardware back button and the software back button on the navigation bar. But i don't understand why.

On iOS I still had too add it myself with a custom Page renderer.

So, is this going to be fixed in 3.6.0?

I think it was moved to In Progress by mistake.

@samhouts so it needs specification now? What's not specified about it?

@ddobrev we still need to spec out the best way to expose what people want. Currently OnBackButtonPressed more specifically maps to the Hardware back button so the features of it more revolve around that. A lot of what people want is more an OnNavigatingBack feature.

Just tying the software back arrow to that method right now still seems more like a hack then providing the best solution

For example just deprecating OnBackButtonPressed and creating

OnHardwareBackButtonPressed and OnNavigatingBack

Well, either way is fine. Just do it. It's been long enough.

As a Page feature would something like this make sense? It would be immensely helpful if we were able to observe and cancel reverse navigation (hence the Task\

enum ReverseNavigationSourceType {
    Unspecified,
    HwButton,
    Navbar,
    Gesture
}

class ReverseNavigationEventArgs {
    public ReverseNavigationSourceType SourceType { get; }
    public object Source { get; }
}

class Page {
    public virtual Task<bool> OnNavigatingBack(ReverseNavigationEventArgs args) {
        return Task.FromResult(true);
    }
}

Second feasible option I can see is implementing this as a part of INavigation interface.

EDIT: slightly modified the API

The @pikausp solution look like the best to me

In addition, I think it would be great to expand this further and introduce something resembling "Page state".

Page can be active or inactive.

class PageActivationEventArgs { }
class PageDeactivationEventArgs {
    public bool IsPermanent { get; }
}
class Page {
    public virtual Task OnActivating(PageActivationEventArgs) {
        return Task.CompletedTask;
    }

    public virtual Task<bool> OnDeactivating(PageDeactivationEventArgs) {
        return Task.FromResult(true);
    }
}

When Page is navigated away from the OnDeactivating is invoked. The value of IsPermanent is based on whether or not the page is kept in navigation stack (true for pops, false for pushes).

Possibly introduce OnActivated and OnDeactivated that are invoked once the navigation is completed.

This gives the developer great power ower navigation flow.

What are your thoughts?

Would you mind reporting any progress here, please?

Any way to increase priority?
I have had workarounds for years now, which sometimes didn't work anymore,...
My current workaround is also a really bad hack with disadvantages, would love to remove my custom navigation page renderers for good and fix some issues in the process.

Bump

Any update on intercepting the soft back button. We are really struggling on this. No solution mentioned in this post works for us.

This is truly needed. Having to either hack or implement your own toolbar is a bit of an overkill for such an obvious omission. Please ...

Any updates here @samhouts ?
Sounds like multiple high-level solutions have been suggested here, all of which would give us the functionality we're looking for.

Would also love to have this feature.

Required for every use case that should prevent data loss. I need this too, every workaround is just too hacky.

Ok, team, we're now leaning towards the functionality described here: https://github.com/xamarin/Xamarin.Forms/issues/6971#issuecomment-574823028

Any objections?

@samhouts I took a quick look at the linked issue and since I haven't used shell yet this is more of a question. BackButtonBehavior is just a wrapper (with some extras) around a command which gets executed a when a user attempts to navigate back?

Basically, it's not about intercepting the reverse navigation but instead taking control over what exactly happens when a user attempts to navigate back via any method available (swipe, navbar button, android button,..). I can decide to navigate the user to a new page instead of navigating back if I decide so, is that correct?

@samhouts I have a working interception in my production app for iOS uwp and Android. iOS and Android using custom Page renderer. When User naviagtes back via Software or Hardware Back-Button I can Show Pop-up "Discard Changes?". When the User confirms the prompt, Back navigation ist executed and Page gets popped, otherweise User stays on current Page. I have Not yet considred that swiping ist also possible to Go Back, thats Missing in my Implementation

@samhouts - It's not clear to me how the proposal allows interception of the back button to add a conditional step.

Does the proposal work like this?

  • if {Shell, NaviationPage}.BackButtonCommand is not assigned, the back button behaves normally and back navigation occurs automatically.
  • If {Shell, NaviationPage}.BackButtonCommand is assigned, then no back navigation occurs automatically. The assigned ICommand instance may explicitly do something like this, depending on whether or not you are using Shell or not.

c# async Task MyProcessBackButton(bool usingShell) { bool navigateBack = await DisplayAlert("Cancel?", "Undo changes", "Yes", "No"); var mainPage = usingShell ? (Page)Shell.Current : Application.Current.MainPage; if (mainPage is MasterDetailPage mdPage) mainPage = mdPage.Detail; if (navigateBack) await mainPage.Navigation.PopAsync(); }

Something like that should work for me but it seems cumbersome. Maybe I'm not looking at the proposal right. It would help to see the use cases spelled out with examples.

P.S. Like @nschoenberg, I also have interception with custom renders in my apps (on NavigationPage, doing something like the MyProcessBackButton method above. It is nice that the proposal is not requiring Shell - it will allow easier adoption for our older apps).

  • if {Shell, NaviationPage}.BackButtonCommand is not assigned, the back button behaves normally and back navigation occurs automatically.

This is how I would imagine it to work, often we want the default functionality if not overriden. This would behave the same as overriding the device back button on android and returning true. Doesnt it make sense to replicate that implemenation in someway?

As a Page feature would something like this make sense? It would be immensely helpful if we were able to observe and cancel reverse navigation (hence the Task). Also, it might be handy to be able to distinguish what caused the request to navigate back.

enum ReverseNavigationSourceType {
    Unspecified,
    HwButton,
    Navbar,
    Gesture
}

class ReverseNavigationEventArgs {
    public ReverseNavigationSourceType SourceType { get; }
    public object Source { get; }
}

class Page {
    public virtual Task<bool> OnNavigatingBack(ReverseNavigationEventArgs args) {
        return Task.FromResult(true);
    }
}

Second feasible option I can see is implementing this as a part of INavigation interface.

EDIT: slightly modified the API

In the issue https://github.com/xamarin/Xamarin.Forms/issues/6971#issuecomment-574823028, I am not sure exactly where the BackButtonCommand will be exposed, is provided only on NavigationPage? Is there a single implemation on a containing NavigationPage and all pages pushed onto the that navigation stack use the same behaviour? (I dont feel this would be a typical use case). How do individual pages implement their own override and take advantage of the command?

I think the proposal provided above would satisfy the developers needs to override per page, since the invocation of back navigation is some what irrelevant often the dev wants to simply control what takes place on the back navigation i.e. prompt some user dialog prior to send them to another page or simply leave as default.

I cant speak for everyone but we have instances in projects where we implemented some function that get excecuted if software button pressed, we also override the OnBackButtonPressed that calls the same function. Two forms of back navigation executing the same code.

These properties all work the same as any of the attached properties on NavigationPage

https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Core/NavigationPage.cs#L19

You can just apply them to the active page and the NavigationPage reacts. The attached properties are nice because they are bindable so you can just set it up on a page and bind it into the VM.

<ContentPage NavigationPage.BackButtonCommand={Bindiing BackCommand}

One of the advantages of having the command just "disable" the default behavior is that it enables async navigating. Because now the button just routes to the command and the command can do whatever for as long as it wants and then it can use navigation apis to complete the navigation if it wants to.

Making it all async is interesting mainly because of scenarios like swiping back or swipe to dismiss on a modal because those aren't natively "async" so what we could also do here is tap into those gestures, route to the command, and cancel the gesture.

We could also make it an event on the Navigation class which could fire all the way up the hierarchical proxy and then with an event you could use an EventToCommand Behavior.

I do also wonder if NavigatingBack makes sense. If we're going to have something like that the might as well just create a navigating event similar to shell.

I realize this is a bit rambling :-) but just wanted to answer some question and get some ideas down for discussion

You can just apply them to the active page and the NavigationPage reacts. The attached properties are nice because they are bindable so you can just set it up on a page and bind it into the VM.

<ContentPage NavigationPage.BackButtonCommand={Bindiing BackCommand}

@PureWeen sorry forgot accessing properties was possible on a pages that way & that makes sense to do it that way anyway.

Would the NavigationPage.BackButtonCommand consume the device back button also or just software back button? Since there are other ways of invoking back navigation.

Currently I'm thinking NavigationPage.BackButtonCommand goes away and we have something more generalized. Just need to give it a bit more thought.

Something tied to pop/push might make sense and match existing apis

Not these exactly but something along these lines

UserRequestedPoppingCommand
UserRequestedPushingCommand

Or possibly PoppingCommand and PushingCommand and then we can have something similar to your arguments that give context.

C# enum NavigationSourceType { Unspecified, HwButton, Navbar, Gesture, FromCode }

I'd like to make it all bindable as well but I haven't quite clicked into an idea I super like yet.

Perhaps something on Navigation that indicates current state of navigation?
So if it's done via command you can ask Navigation if it's a gesture in progress or other

Any update on this? We have been waiting on this for over a year.

Any update on this? We have been waiting on this for over a year.

Relax. A year is nothing. SplitView with MasterDetail on iPad was reported over 4 years ago that it does not work and is bugged. At this time, Xamarin.Forms was still using Bugzilla.

@SebastianKruseWago That is a completely different scenario than expected functionality that already exists in a different area. They also fixed a "bug" that allowed a work around. So a year is a lot, for something that should of been included from day one.

@akemper1 SplitView should also work from day one. It is also a bug because it is only a Problem with the iPad MasterDetailRenderer. It has worked before 2.4 or so with a ugly workaround. So I don't think it's different.

Hello, is there any news?

@scriptBoris Possible work arounds are as followed in these links, one solution might fit better then the other depending on your project structure.

https://forums.xamarin.com/discussion/67854/android-menu-and-back-button-not-working

https://theconfuzedsourcecode.wordpress.com/2017/03/12/lets-override-navigation-bar-back-button-click-in-xamarin-forms/

Ive gotten both solutions from the links in seperate projects.

Good afternoon, I apologize for disturbing you, but is there any news to solve this problem? О.О

I am updating the question :)

<ContentPage NavigationPage.HasBackButton="False">

Is there any update on this? The solutions presented in this thread are more than acceptable, its been years and people are waiting for this.

Can anyone from the Xamarin team comment on the status of this? This really is a big issue for many people.

I know you can do this in shell, but for many larger apps that take advantage of Prism or MVVMCross shell is not fully supported so this is something we really need.

Thanks

Apologies @akemper1 but no specific updates currently.

We haven't forgot about this one but are just a bit delayed on an official API to settle on. There are some pending Shell PRs that influence Back Button that will propagate over to the APIs used here

I agree with many that are saying this is a big issue for them.

I am going to give some of the workarounds a try though. I just want to be able to prevent a user going back until they "confirm" that they will lose changes on the page they are on.

any news about this? it's been a really long time :(

Was this page helpful?
0 / 5 - 0 ratings