3

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?

Nodoid
  • 1,449
  • 3
  • 24
  • 42
  • have you tried setting your breakpoint at the start of the handler? – Jason Jul 28 '21 at 13:29
  • Yes - doesn't get hit at all despite the code executing. – Nodoid Jul 28 '21 at 13:48
  • This issue also affects properties changing in the VM and UI not reacting (for example, if I set IsBusy in the VM, a progress bar doesn't show - so it looks like the mvvm part is not talking to the UI at all) – Nodoid Jul 28 '21 at 14:02
  • this is tagged windows-community-toolkit, are you actually using xamarin community toolkit? What base class are you using for your VM? – Jason Jul 28 '21 at 14:05
  • I'm using the communitytoolkit.mvvm which is a branch from the windows.communitytoolkit. I use the Xam.CommuniutyToolkit in my UI for certain UI widgets (extender view) – Nodoid Jul 28 '21 at 14:51
  • Any errors or warning in log, at the time the property is changed? Is there a CommunityToolkit initialization line you have to put before or after xamarin forms init line, in your activity.cs or whereever? Try deleting bin and obj folders, to make sure solution is totally clean? To double-check Jason's first question: a breakpoint on `switch (e.PropertyName)` doesn't get hit either? – ToolmakerSteve Jul 28 '21 at 20:00
  • Also, `ViewModel` is definitely the same object as `BindingContext`? You set both to same instance in view's constructor? (Something like `ViewModel = new MyViewModel(); BindingContext = ViewModel;`) – ToolmakerSteve Jul 28 '21 at 20:12
  • *"The image shows"* *What* image shows? You don't show any code that would show an image. How can you be sure there is a relationship between some image showing, and LoginOK being set? Most importantly, **are you sure that the value of LoginOK changed?** If it starts `false`, and stays `false`, there is no change. AFAIK, it is "not specified" whether "setting a property to a value it already has" should trigger OnPropertyChanged. That's probably "optimized out", to do nothing. – ToolmakerSteve Jul 28 '21 at 20:17

1 Answers1

0

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.
Bijington
  • 3,661
  • 5
  • 35
  • 52
  • Doesn' the RelayCommand do the same as the ICommand though? I'll try the other methods and see what gives - thanks for the pointers. – Nodoid Jul 30 '21 at 11:22
  • @Nodoid oh sorry my head is stuck in Xamarin.Forms… yes RelayCommand is probably what you need. I’ll update the answer – Bijington Jul 30 '21 at 11:32