Intercepting property changes with CommunityToolKit.Mvvm amd Xamarin Forms

Viewed 280

I've been moving code from mvvmlight to the CommunityToolkit.Mvvm framework with my Xamarin Forms project and have hit a snag.

In mvvmlight, I would have a property like this

bool loginOK;
public bool LoginOK
{
    get => loginOK;
    set => Set(()=>LoginOK, ref loginOK, value, true);
}

in CommunityToolkit.Mvvm, this becomes

bool loginOK;
public bool LoginOK
{
     get => loginOK;
     set => SetProperty(ref loginOK, value);
}

Accorrding to the docs, if the property changes, the PropertyChanging event is fired

In my code behind in (in Xam.Forms), I have this

protected override void OnAppearing()
{
    base.OnAppearing();
    ViewModel.PropertyChanged += ObservableObject_PropertyChanged;
}

async void ObservableObject_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
    switch (e.PropertyName)
    {
            case "LoginOK":
                if (ViewModel.LoginOK)
                {
                    if (ViewModel.SkipWelcome)
                    {
                        var d1 = await image.ScaleTo(4, 500);
                        if (!d1)
                        {
                            Device.StartTimer(TimeSpan.FromMilliseconds(500), () =>
                            {
                                Device.BeginInvokeOnMainThread(async () => await Shell.Current.GoToAsync("//TabBar"));
                                return false;
                            });
                        }
                    }
                }
                else
                {
                    var d2 = await image.ScaleTo(8, 500);
                    if (!d2)
                    {
                        var d3 = await image.ScaleTo(0, 500);
                        if (!d3)
                        {
                            Device.StartTimer(TimeSpan.FromMilliseconds(500), () =>
                            {
                                Device.BeginInvokeOnMainThread(async () => await Shell.Current.GoToAsync("//Welcome"));
                                return false;
                            });
                        }
                    }
                }
                break;
        }
    }

When I run this and set a break point on the line

var d2 = await image.ScaleTo(8,500);

The break is not hit, but the imge shows

Am I doing something wrong to intercept the property changing or is there something I'm missing from the docs?

1 Answers

The main issue you are seeing is because loginOK defaults to false and when a login attempt fails LoginOK does not technically change and therefore SetProperty does not result in raising the PropertyChanged event. If you look at the codebase for the toolkit you will notice that they always check whether the value has changed before raising any notifications.

There are a number of options that you could consider to work around this problem:

1 - switch to bool?

If you switch to using a nullable bool then it's value will default to null and therefore when a login attempt fails false will cause PropertyChanged to fire.

2 - use an enum

Similar to the bool? approach in point 1 but instead use something like a LoginResult enum like the following:

public enum LoginResult
{
    None = 0,
    Failed,
    Success
}

This will make you code much more readable than point 1.

3 - fire a specific event

Rather than rely on PropertyChanged you could create your own event (e.g. LoginAttempted) you could then keep your bool indicating success or not if you so desired.

4 - make use of a Command

Events are sometimes consider a bad concept of the past so you could instead create an ICommand property on your View and assign it to your View Model allowing the View Model to call Execute on it and pass the log result up to the view. Something like:

View

public ViewConstructor()
{
    ViewModel.LoginAttemptCommand = new RelayCommand<bool>(() => OnLoginAttempted());
}

public void OnLoginAttempted(bool result)
{
    // Old logic on whether login succeeded.
}

ViewModel

public ICommand LoginAttemptCommand { get; set; }

// Wherever you set LoginOK = false/true call:
LoginAttemptCommand.Execute(false); // or true if it succeeds.
Related