How to get Navigation back button event in Xamarin forms

In my project on one page there is a form to add products.
if user doesn't save the details and pressed back button, at that time I want to display the action sheet having Discard, Save, Cancel options.
I'm unable to get the back button pressed event.
I'm working in Xamarin studio and used cross platform with xamarin form

I've already tried OnBackButtonPressed event but it doesn't get call

protected override bool OnBackButtonPressed()
{

Device.BeginInvokeOnMainThread(async() => {
    var result = await DisplayActionSheet("Action","Cancel",null,"Discard","Save");
        //if (result) await this.Navigation.PopAsync(); // or anything else
    });

return base.OnBackButtonPressed();

}

Answers

  • AlessandroCaliaroAlessandroCaliaro ITMember ✭✭✭✭✭

    Are you talking about hardware back button or navbar back button?

  • AndrewCaunce.6539AndrewCaunce.6539 USMember ✭✭

    navigationbar back button

  • AlessandroCaliaroAlessandroCaliaro ITMember ✭✭✭✭✭

    Onbackbuttonpressed works only for hardware button. I think XF does not has an event for navbar button. A custom renderer could help you or you can hide the navbar button

  • Maharshi.5212Maharshi.5212 USMember ✭✭

    Will OnDisappearing work?

  • NMackayNMackay GBInsider, University mod
    edited March 2016

    @AndrewCaunce.6539

    This topic is discussed extensively on the forum, this thread is just one example.

    https://forums.xamarin.com/discussion/46983/how-to-intercept-navigation-bar-back-button-clicked-in-xamarin-forms

    As @TorbenKruse suggests, I'd recommend using a modal dialog for screens like add/edit. This gets rounds trying to override the back button which just frustrates users anyway. Any app that doesn't allow me to go back gets binned of my device quick smart. In Android you can override OnBackPressed to handle the hardware button trying to dismiss the modal.

  • AdamPAdamP AUUniversity ✭✭✭✭✭
    edited March 2016

    @AndrewCaunce.6539 - as @NMackay mentions Modal's are certainly the cross platform way, where there would be no back button, just a save or cancel button on your page that then switches you back to your other navigation stack, rather than a blocking dialog.

    However I was unfortunately way too far into my project to rectify my mistake and I was developing Windows first, then the other platforms, hence why I missed it. So here are my super hacks :) Please note they are hacks, not proud but gets the job done.

    Windows

    • No need OnBackButtonPressed triggered all the time as only hardware button

    Android

    • OnBackButtonPressed works when the hardware back button is pressed, otherwise it doesn't get called and hence

    Note: I have a MasterDetailPage as the root object, then a NavigationPage, then the Page and in my ViewModel I have the function that gets called with OnBackButtonPressed. The OnBackButtonPressed is what shows the dialog and gets the answer to go back or not.

    In your MainActivity.cs on Android do this

        public override bool OnOptionsItemSelected(IMenuItem item)
        {
            if (item.ItemId == 16908332)
            {
                var navPage = ((app.MainPage.Navigation.ModalStack[0] as MasterDetailPage).Detail as NavigationPage);
    
                if (app != null && navPage.Navigation.NavigationStack.Count > 0)
                {
                    int index = navPage.Navigation.NavigationStack.Count - 1;
    
                    var currentPage = navPage.Navigation.NavigationStack[index];
    
                    var vm = currentPage.BindingContext as ViewModel;
    
                    if (vm != null)
                    {
                        var answer = vm.OnBackButtonPressed();
                        if (answer)
                            return true;
                    }
    
                }
            }
    
            return base.OnOptionsItemSelected(item);
        }
    

    iOS

    • OnBackButtonPressed is never called because it doesn't have a hardware back button.

    Note: I put a property on my BasePage of OverrideBackButton so that I only did it on certain pages.

    You need to write a custom renderer for your page.

    [assembly: ExportRenderer(typeof(Page), typeof(CustomPageRenderer))]
    namespace Mobile.iOS.CustomRenderer
    {
        public class CustomPageRenderer : PageRenderer
        {
    
            public override void ViewWillAppear(bool animated)
            {
                base.ViewWillAppear(animated);
    
                var page = Element as CorePage;
    
                if (page != null)
                {
                    if ((page).OverrideBackButton)
                    {
                        var root = this.NavigationController.TopViewController;
                        // NOTE: this doesn't look exactly right, you need to create an image to replicate the back arrow properly
                        root.NavigationItem.SetLeftBarButtonItem(new UIBarButtonItem("< Back", UIBarButtonItemStyle.Plain, (sender, args) =>
                        {
                            var navPage = page.Parent as NavigationPage;
                            var vm = page.BindingContext as ViewModel;
    
                            if (vm != null)
                            {
                                var answer = vm.OnBackButtonPressed();
    
                                if (!answer)
                                    navPage.PopAsync();
                            }
                            else
                                navPage.PopAsync();
                        }), true);
                    }
                }    
            }
        }
    }
    
  • NMackayNMackay GBInsider, University mod

    @AdamP

    Apologies adam, I referred to you by accident. Thanks for posting your 'hacks' though, that probably took a fair bit of hair pulling to discover and implement :smile:

  • AdamPAdamP AUUniversity ✭✭✭✭✭

    @NMackay - no problems, good I was tagged in anyway, as yes it took some hair pulling to figure it out :) Maybe will help someone else who ended up in my position.

  • StephenGrahamStephenGraham GBMember ✭✭

    There may be a simpler solution. The back button in the navigation bar can be removed...
    In Xaml:- NavigationPage.HasBackButton="False"
    In page's contructor:- NavigationPage.SetHasBackButton(this, false);

    Without a navigation button to tap there is arguably no tap to respond to, although the navigation bar does remain visible.

    I have tried setting the SetHasBackButton dynamically in response to other events but the back button remains visible (although unresponsive). This feels more like a bug and I have only tested on Android so far.

  • BrightLeeBrightLee KRMember ✭✭✭

    @AdamP

    Hi, Is that the way how you cleaned up your page?
    I'm interesting to know how to clean up ConentPage.

    I thought It should be cleaned up automatically when press back button.
    Today, I noticed it does not!

    Wow. ContentPages are still remained without parent!
    What the hack.
    And StartTimer() is running continously.

    Can you guide about this little bit?

    Is that how you cleaned up your page?
    That seems good.

  • AdamPAdamP AUUniversity ✭✭✭✭✭

    @BBright - The above code is for overriding the Back Button and either staying on the page or moving back. I think your question is a little different.

    A content page will be cleaned up automatically (and eventually) when it is no longer referenced.

    If you are still seeing you ContentPage in memory then you are either keeping a reference to it, or it could possibly be a XF bug that is keeping a reference to it.

  • BrightLeeBrightLee KRMember ✭✭✭
    edited September 2016

    @AdamP
    Hi Adam.
    Thanks for your answer.
    I have no reference to my ContentPage but there is Device.StartTimer in it,
    Is there possibility that Starttimer keeps page's reference before I set return value as false? (I know timer is static so if it wants to call, it should have its reference. right?)

    If so, I think I should have mechanism putting return value as false.

  • BrightLeeBrightLee KRMember ✭✭✭

    @AdamP
    or Using stoppable timer and start it only in OnAppearing and stop in OnDisappearing.
    Is it better solution?

  • AdamPAdamP AUUniversity ✭✭✭✭✭

    @BBright - if you have a Timer inside your content page that references an element on your ContentPage then yes it will keep a reference.

    If you Timer is only related to doing something on the UI then yes stop and start it via OnAppearing / OnDisappearing.

    Otherwise you might want to stop it when OnPopped is called in the navigation page. It all depends on what your timer is doing.

  • BrightLeeBrightLee KRMember ✭✭✭
    edited September 2016

    @AdamP

    Thanks so much again for many times.
    Can I have last question?

    Member var of ContentPage that I declared like XXView, XXLabel, XXLayout,
    should I put null at page popped?
    and the members that I named in xaml with using “x:Name” should be put null at the last time?

    It does not make sense but I want to make sure..

    If ContentPage is gathered by GC and it’s done(no need to put null), it would be the best.

    Thanks!

  • AdamPAdamP AUUniversity ✭✭✭✭✭

    @BBright - there is no need to null them out if they aren't referenced by anything external to the page as when the page gets de-referenced, all the controls will only be referenced by the page which isn't referenced by anything hence it will all be cleaned up by GC.

  • BrightLeeBrightLee KRMember ✭✭✭

    Yes that makes sense.
    Thanks for many times!
    @AdamP

  • pauldpauld USMember ✭✭

    Many people seem to have this requirement because they want to prevent or cancel back navigation.

    I simply want to know when it's happened, because each page in our app represents a stage in a process model that I need to wind backwards if the user goes 'back'.

    In our iOS app I override UINavigationController.PopViewController(). I find it hard to believe that Xamarin.Forms doesn't support the equivalent.

  • StephenGrahamStephenGraham GBMember ✭✭

    @AdamP

    Thanks for your posts. I also didn't realise that the navigation back button was an issue for Android until it was too late. Your solutions helped a lot.

    In case this helps anyone else, my solution was a little different. The code in my OnBackButtonPressed events resets flags preventing double tapping buttons etc. so the easiest solution for me was to get the current page from the navigation stack and call SendbackButtonPressed to execute existing code in the page.

        public override bool OnOptionsItemSelected(IMenuItem item)
            {
                if (item.ItemId == 16908332)
                {
                    // Get the index for the current page.
                    int index = ((App)App.Current).MainPage.Navigation.NavigationStack.Count - 1;
                    // Get the current page.
                    Page currentPage = ((App)App.Current).MainPage.Navigation.NavigationStack[index];
                    // Programmatically press the hardware back button.
                    if (currentPage.SendBackButtonPressed())
                    {
                        return true;
                    }
                    else
                    {
                        return base.OnOptionsItemSelected(item);
                    }
                }
                else
                {
                    return base.OnOptionsItemSelected(item);
                }
            }
    
  • AlexutziusAlexutzius ROMember

    @Maharshi.5212 said:
    Will OnDisappearing work?

    I did like this and it worked.

    protected override void OnDisappearing()
    {
    //do something
    }

    Thanks.

  • DomKapDomKap USMember ✭✭

    Hi.
    @StephenGraham I follow your code but is a bit non clear for me.
    I read some other topics about how to overdrive "Navigation back button" but still doesn't know how to do it. That code you posted I should add to MainAcivity.cs.
    OK, but what code shall I add to my page in xamarin forms to overdrive nav back button and display for example "Alert"?

    What I want to achieve is:
    My starting page is MainPage, from there user can go to NewNoteView page. And there if user tapped "Navigation back button" I want to display alert or fired other function.

    Can you tell me how to do it, please.

    Thank you.

  • StephenGrahamStephenGraham GBMember ✭✭

    @DomKap said:
    Hi.
    @StephenGraham I follow your code but is a bit non clear for me.
    I read some other topics about how to overdrive "Navigation back button" but still doesn't know how to do it. That code you posted I should add to MainAcivity.cs.
    OK, but what code shall I add to my page in xamarin forms to overdrive nav back button and display for example "Alert"?

    What I want to achieve is:
    My starting page is MainPage, from there user can go to NewNoteView page. And there if user tapped "Navigation back button" I want to display alert or fired other function.

    Can you tell me how to do it, please.

    Thank you.

    Hi @DomKap,

    You are correct, the code for OnOptionsItemSelected needs to be added to the MainActivity.cs class in your Android project. The code gets the page on which the navigation button was tapped and programmatically calls the SendBackButtonPressed method of that page. To respond to this you need to code the OnBackButtonPressed event for the page, in your case the NewNoteView page. I have reproduced below the OnBackButtonPressed event code for one of my pages - essentially the user is shown a toast message if the current order is being processed. Bear in mind that the OnBackButtonPressed event is also triggered when the user taps the hardware back button.

    Hope this helps.

        protected override bool OnBackButtonPressed()
        {
            // Prevent navigation while still processing.
            if (((App)App.Current).IsBusy || isBackButtonPressed)
            {
                return true;
            }
            else
            {
                isBackButtonPressed = true;
                // Get the current order header. 
                OrderHeaderViewModel orderHeader = ((App)App.Current).OrderHeader;
                if (orderHeader.Locked)
                {
                    Device.BeginInvokeOnMainThread(async () =>
                    {
                        IToastNotificator toastNotificator = DependencyService.Get<IToastNotificator>();
                        Task<bool> taskToast = toastNotificator.Notify(
                            ToastNotificationType.Warning,
                            Resource.Confirm_Toast_Title_Locked,
                            Resource.Confirm_Toast_Message_Locked,
                            TimeSpan.FromSeconds(((App)App.Current).Settings.ToastTimeout));
                        await taskToast.ContinueWith((Task<bool> taskToastResult) =>
                        {
                            isBackButtonPressed = false;   
                        });
                    });
                    return true;
                }
                else
                {
                    ((App)App.Current).IsAnimated = false;
                    isBackButtonPressed = false;
                    return base.OnBackButtonPressed();
                }
            }
        }  
    
  • DomKapDomKap USMember ✭✭

    Hi. Thank you for your response.
    Can you write what namespaces I should use, because I have many errors and VS don't give me any suggestions which I should add.

  • StephenGrahamStephenGraham GBMember ✭✭

    Hi @DomKap,

    In the MainActivity.cs file (Android project) I use the following namespaces:-

    using Android.App;
    using Android.Content.PM;
    using Android.OS;
    using Android.Views;
    using PCLStorage;
    using Plugin.Toasts;
    //using Android.Media;
    using System;
    using System.IO;
    using Xamarin.Forms;
    using Xamarin.Forms.Platform.Android;
    

    I believe that the Xamarin.Forms and Xamarin.Forms.Platform.Android namespaces are the minimum needed for the OnOptionsItemSelected method to work.

    In the class for my page I use the following namespaces:-

    using AssetRentalMobile.Resources; // Project resources.
    using AssetRentalMobile.ViewModel; // Project View models.
    using Plugin.Geolocator;
    using Plugin.Geolocator.Abstractions;
    using Plugin.Toasts;
    using SignaturePad.Forms;
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Threading.Tasks;
    using Xamarin.Forms;
    

    Again, I believe that the System.Threading.Tasks and Xamarin.Forms namespaces are the minimum needed for the Device.BeginInvokeOnMainThread call. If you are attempting to display a toast, you will need the Plugin,Toasts namespace in both classes (assuming that you have installed the Toasts plugin - Note I am using an old version of the Toasts plugin so your code may differ).

    Other ((App)App.Current) properties are defined in App.xaml.cs so these shouldn't need a namespace.

  • DomKapDomKap USMember ✭✭

    I don't know why I have so many errors apart that not instaled Toast plugin.
    If you can check my printscreen.

  • StephenGrahamStephenGraham GBMember ✭✭

    Hi @DomKap,

    The OnBackButtonPressed event shown in my previous posting was taken directly from my project, intended as an example of what the code could look like. From what I can see from your screen shot, the errors refer to properties of the ((App)App.Current) object (IsAnimated, IsBusy, OrderHeader, Settings etc.), the variable isBackButtonPressed and the Resource classes. These properties and variables are what must be checked before MY project allows a toast message to appear. You must decide what conditions you want to check YOUR NewNoteView class before allowing a toast message to appear. If you have none, your OnBackButtonPressed method could be as simple shown below...

        protected override bool OnBackButtonPressed()
        {
            IToastNotificator toastNotificator = DependencyService.Get<IToastNotificator>();
            await toastNotificator.Notify(
                ToastNotificationType.Warning,
                    "Toast Title",
                    "This is the toast message",
                    TimeSpan.FromSeconds(3);
    
            return base.OnBackButtonPressed();
        }  
    
  • DomKapDomKap USMember ✭✭

    But it still doesn't affect when user tapped navigation back button. It only override hardware back button. I asked about navigation back button.

  • BhautikBhautik Member ✭✭✭

    @AdamP said:
    @AndrewCaunce.6539 - as @NMackay mentions Modal's are certainly the cross platform way, where there would be no back button, just a save or cancel button on your page that then switches you back to your other navigation stack, rather than a blocking dialog.

    However I was unfortunately way too far into my project to rectify my mistake and I was developing Windows first, then the other platforms, hence why I missed it. So here are my super hacks :) Please note they are hacks, not proud but gets the job done.

    Windows

    • No need OnBackButtonPressed triggered all the time as only hardware button

    Android

    • OnBackButtonPressed works when the hardware back button is pressed, otherwise it doesn't get called and hence

    Note: I have a MasterDetailPage as the root object, then a NavigationPage, then the Page and in my ViewModel I have the function that gets called with OnBackButtonPressed. The OnBackButtonPressed is what shows the dialog and gets the answer to go back or not.

    In your MainActivity.cs on Android do this

        public override bool OnOptionsItemSelected(IMenuItem item)
        {
            if (item.ItemId == 16908332)
            {
                var navPage = ((app.MainPage.Navigation.ModalStack[0] as MasterDetailPage).Detail as NavigationPage);
    
                if (app != null && navPage.Navigation.NavigationStack.Count > 0)
                {
                    int index = navPage.Navigation.NavigationStack.Count - 1;
    
                    var currentPage = navPage.Navigation.NavigationStack[index];
    
                    var vm = currentPage.BindingContext as ViewModel;
    
                    if (vm != null)
                    {
                        var answer = vm.OnBackButtonPressed();
                        if (answer)
                            return true;
                    }
    
                }
            }
    
            return base.OnOptionsItemSelected(item);
        }
    

    iOS

    • OnBackButtonPressed is never called because it doesn't have a hardware back button.

    Note: I put a property on my BasePage of OverrideBackButton so that I only did it on certain pages.

    You need to write a custom renderer for your page.

    [assembly: ExportRenderer(typeof(Page), typeof(CustomPageRenderer))]
    namespace Mobile.iOS.CustomRenderer
    {
        public class CustomPageRenderer : PageRenderer
        {
    
            public override void ViewWillAppear(bool animated)
            {
                base.ViewWillAppear(animated);
    
                var page = Element as CorePage;
    
                if (page != null)
                {
                    if ((page).OverrideBackButton)
                    {
                        var root = this.NavigationController.TopViewController;
                        // NOTE: this doesn't look exactly right, you need to create an image to replicate the back arrow properly
                        root.NavigationItem.SetLeftBarButtonItem(new UIBarButtonItem("< Back", UIBarButtonItemStyle.Plain, (sender, args) =>
                        {
                            var navPage = page.Parent as NavigationPage;
                            var vm = page.BindingContext as ViewModel;
    
                            if (vm != null)
                            {
                                var answer = vm.OnBackButtonPressed();
    
                                if (!answer)
                                    navPage.PopAsync();
                            }
                            else
                                navPage.PopAsync();
                        }), true);
                    }
                }    
            }
        }
    }
    

    What is CorePage in iOS example and if it is working then how to use in xamal page.Can you please example?

  • dev.kramdev.kram PHMember ✭✭

    I strongly discouraged the use of Magic Numbers here. Instead of using 16908332 in OnOptionsItemSelected, use Android.Resource.Id.Home instead.

Sign In or Register to comment.