Forum Xamarin.Forms

Preventing hardware back button from bypassing form validation with Xamarin Forms/ Prism/ MVVM

ianmchughianmchugh Member
edited June 2020 in Xamarin.Forms

I'm a newbie to Xamarin Forms and Prism. I have a Xamarin form with a ListView which allows editing of the list items by calling another Edit view (Upsert). There is a button on the Upsert view that validates the user's input and if valid, saves the entry to a SQLite database and returns the user to the original form (with the ListView). If it is not valid, the desired behaviour is to return to the form for correction or to allow the user to cancel and to revert to the last valid entity.

The default behaviour of the hardware back button is to leave the form without validating and to remove the parameters!

I was unable to find a behaviour to control the hardware back button, so I wrote the following View Model. This view model works fine and implements the exact behaviour that I want, but I can't help thinking that it's too complicated.

There are two classes in this sample: Mailing List (parent entity) and Mailing List Item (child entity). This is the View Model for creating/editing the Mailing List Item.

Am I missing something simple? Any comments would be appreciated...

using Prism.Commands;
using System;
using System.Diagnostics;
using Prism.Common;
using Prism.Navigation;
using Rsvp.Entities.Shared;
using Rsvp.Xamarin.App.Services;
using Xamarin.Forms;
using Rsvp.Xamarin.App.Helpers;
using Rsvp.Entity.Abstractions.EF.UnitOfWork;
using Prism.AppModel;
using Prism.Services;

namespace Rsvp.Xamarin.App.ViewModels
{
    public class MailingListItemUpsertViewModel :  ViewModelBase, INavigationAware, IPageLifecycleAware
    {
        public DelegateCommand<object> SaveCommand { get; private set; }
        private INavigationService navigationService;
        private MailingListItemService syncService;
        private IPageDialogService dialogService;

        //private Page page = PageUtilities.GetCurrentPage(Application.Current.MainPage);


        public MailingListItemUpsertViewModel(
            MailingListItemService syncService, 
            INavigationService navigationService, 
            IPageDialogService dialogService) : base(navigationService)
        {
            this.syncService = syncService;
            this.dialogService = dialogService;
            this.navigationService = navigationService;
            SaveCommand = new DelegateCommand<object>(SaveMailingListItem);
        }

        private string _title;
        public string Title
        {
            get { return _title;}
            set { SetProperty(ref _title, value); }
        }

        private MailingList _mailingList;
        public MailingList MailingList
        {
            get { return _mailingList; }
            set { SetProperty(ref _mailingList, value); }
        }

        private MailingListItem _mailingListItem;
        public MailingListItem MailingListItem
        {
            get { return _mailingListItem; }
            set { SetProperty(ref _mailingListItem, value); }
        }

        // these represent the validatable data items in the entity.  This validation rules
        // themselves are annotations on the MailingListItem object.
        private string originalEmail;
        private string originalFirstName;
        private string originalLastName;
        private string originalSalutation;


        public void OnNavigatedFrom(INavigationParameters parameters)
        {
        }

        public void OnNavigatedTo(INavigationParameters parameters)
        {
            MailingListItem = parameters.GetValue<MailingListItem>("mailingListItem");

            // these represent the validatable data items in the entity.
            originalEmail = parameters.GetValue<string>("originalEmail") ?? MailingListItem.Email;
            originalSalutation = parameters.GetValue<string>("originalSalutation") ?? MailingListItem.Salutation;
            originalFirstName = parameters.GetValue<string>("originalFirstName") ?? MailingListItem.FirstName;
            originalLastName = parameters.GetValue<string>("originalLastName") ?? MailingListItem.LastName;

            // This is the parent entity
            MailingList = parameters.GetValue<MailingList>("mailingList");

            Title = MailingList.Name;
        }

        async void SaveMailingListItem(object p)
        {
            Page page = PageUtilities.GetCurrentPage(Application.Current.MainPage);
            if (!ValidationHelper.IsFormValid(MailingListItem, page))
            {
                return;
            }

            ContinueOrDiscard(true);

        }

        public async void OnAppearing()
        {
            Page page = PageUtilities.GetCurrentPage(Application.Current.MainPage);


            // Validates the page data on appearing by applying the annotations on the MailingListItem object.
            // The only way they should be invalid is if the hardware back button has been pressed causing the 
            // form to be re-called.
            // 
            bool isValid = ValidationHelper.IsFormValid(MailingListItem, page);
        }

        public void OnDisappearing()
        {
            ContinueOrDiscard();
        }


        public async void ContinueOrDiscard(bool alreadyValidated = false)
        {

            var parameters = new NavigationParameters
            {
                { "mailingList", MailingList },
                { "mailingListItem", MailingListItem },
                { "originalEmail", originalEmail },
                { "originalSalutation", originalSalutation},
                { "originalFirstName", originalFirstName },
                { "originalLastName", originalLastName }
            };

            Page page = PageUtilities.GetCurrentPage(Application.Current.MainPage);
            if (alreadyValidated || ValidationHelper.IsFormValid(MailingListItem, page))
            {
                if (MailingListItem.Id == Guid.Empty)
                {
                    await syncService.AddAsync(MailingListItem);
                }
                else
                {
                    await syncService.UpdateAsync(MailingListItem.Id, MailingListItem);
                }
                await navigationService.NavigateAsync(
                    "/MainPage/NavigationPage/MainDetailPage/MailingListIndexPage/MailingListItemIndexPage",
                    parameters, false);
            }
            else
            {
                if (await dialogService.DisplayAlertAsync("This mailing list item is invalid.", "Would you like to continue editing the record?",
                    "Yes",
                    "No"))
                {
                    await navigationService.NavigateAsync(
                        "/MainPage/NavigationPage/MainDetailPage/MailingListIndexPage/MailingListItemIndexPage/MailingListItemUpsertPage",
                        parameters, false);
                }
                else
                {
                    MailingListItem.Email = originalEmail;
                    MailingListItem.Salutation = originalSalutation;
                    MailingListItem.FirstName = originalFirstName;
                    MailingListItem.LastName = originalLastName;
                    await navigationService.NavigateAsync(
                        "/MainPage/NavigationPage/MainDetailPage/MailingListIndexPage/MailingListItemIndexPage", parameters, false);

                }

            }

        }

    }
}

Here is the Xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:prism="http://prismlibrary.com"
             prism:ViewModelLocator.AutowireViewModel="True"
             x:Class="Rsvp.Xamarin.App.Views.MailingListItemUpsertPage"
             Title="{Binding Title}">
    <StackLayout  Padding="20,20,20,20">
        <StackLayout IsVisible="True" BindingContext="{Binding MailingListItem}">
        <Entry Text="{Binding Salutation}" IsVisible="True" IsEnabled="True" Placeholder="Salutation"/>
            <Label x:Name="MailingListItem_SalutationError" IsVisible="False" TextColor="Red" />
        <Entry Text="{Binding FirstName}" IsVisible="True" IsEnabled="True" Placeholder="First name"/>
            <Label x:Name="MailingListItem_FirstNameError" IsVisible="False" TextColor="Red" />
        <Entry Text="{Binding LastName}" IsVisible="True" IsEnabled="True" Placeholder="Last name"/>
            <Label x:Name="MailingListItem_LastNameError" IsVisible="False" TextColor="Red" />
        <Entry Text="{Binding Email}" IsVisible="True" IsEnabled="True" Placeholder="Email address"/>
            <Label x:Name="MailingListItem_EmailError" IsVisible="False" TextColor="Red" />
            <StackLayout Orientation="Horizontal">
                    <Switch IsToggled="{Binding DoNotMail}" IsEnabled="True" HorizontalOptions="Start" />
                <Label Text="Do not email" HorizontalOptions="FillAndExpand" VerticalTextAlignment="Center"/>
            </StackLayout>
        </StackLayout>
        <Button Text="Ok" Command="{Binding SaveCommand}" CommandParameter="{Binding MailingListItem}"/>
</StackLayout>

</ContentPage>
Sign In or Register to comment.