xamarin.formsxamarin-shell

navigating back doesn't remember entered data


I'm trying to create a login flow in Xamarin Forms Shell that consists of a first page, where the user enters their email address, and a second page where they enter a password. I want the user to be able to go back from the password page and alter the entered email address.

This all works fine, if when initially navigating to the first page, I DONT set the email via a query parameter... If I do, when the user goes back form the password page to the email address page, the initially passed in email is shown - regardless of any changes the user may have made to it.

EG:

If the email page is initially navigated to using this code:

await Shell.Current.GoToAsync($"{nameof(test1)}");

The email address is blank by default. If the user changes it to hello@world.com and "continues" to the password page, then navigates back, the email address is still hello@world.com as expected.

However, if the email page is initially navigated to using this code:

var testEmail = "invalid$$3";    
await Shell.Current.GoToAsync($"{nameof(test1)}?EmailAddress={testEmail}");

The email address shows as invalid$$3 as expected. If the user then changes it to hello@world.com and "continues" to the password page, the password page shows hello@world.com in the label as expected, but if the user navigates back, the email address reverts to invalid$$3 on the email entry page (first page).

Here's my viewmodel:

[QueryProperty(nameof(EmailAddress), nameof(EmailAddress))]
    public class LoginViewModel : BaseViewModel
    {
        string emailAddress;
        public string EmailAddress
        {
            get
            {
                return emailAddress;
            }
            set
            {
                SetProperty(ref emailAddress, Uri.UnescapeDataString(value));
            }
        }
    }

Heres the first page (email entry):

<?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:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"             
             xmlns:vm="clr-namespace:The_In_Plaice.ViewModels"             
             mc:Ignorable="d"
             x:Class="The_In_Plaice.Views.test1">
    <ContentPage.BindingContext>
        <vm:LoginViewModel/>
    </ContentPage.BindingContext>
    <ContentPage.Content>
        <StackLayout>
            <Entry x:Name="txtEmail" Text="{Binding EmailAddress}" Placeholder="email"/>
            <Button Clicked="Button_Clicked"/>
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

(code behind)

[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class test1 : ContentPage
{
    public test1()
    {
        InitializeComponent();
    }

    private async void Button_Clicked(object sender, EventArgs e)
    {
        var email = txtEmail.Text;
        await Shell.Current.GoToAsync($"{nameof(test2)}?EmailAddress={email}");
    }
}

and the second page:

<?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:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"             
             xmlns:vm="clr-namespace:The_In_Plaice.ViewModels"             
             mc:Ignorable="d"
             x:Class="The_In_Plaice.Views.test2">
    <ContentPage.BindingContext>
        <vm:LoginViewModel/>
    </ContentPage.BindingContext>
    <ContentPage.Content>
        <StackLayout>
            <Label Text="{Binding EmailAddress}"/>
            <Entry Text="{Binding Password}" IsPassword="True" Placeholder="password"/>
            <Button/>
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

(code behind)

 [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class test2 : ContentPage
    {
        public test2()
        {
            InitializeComponent();
        }
    }

Any ideas what I'm doing wrong here?


Solution

  • The solution I went for here was to implement a "hack" based on ToolMakerSteve's comment.

    The changes below were added to the first page code behind, which basically just stores the current Email Address on disappearing, and replaces whatever is in the viewmodel in the on appearing (if it has been set).

    The only caveat would be if you wanted to change it in a subsequent page, this hack wouldn't work, but for my purposes, it works just fine.

            private string EditedEmailAddress;
            protected override void OnAppearing()
            {
                if (!string.IsNullOrEmpty(EditedEmailAddress))
                    ViewModel.EmailAddress = EditedEmailAddress;            
                base.OnAppearing();
            }
    
            protected override void OnDisappearing()
            {
                EditedEmailAddress = ViewModel.EmailAddress;
                base.OnDisappearing();
            }