0

I'm trying to have a ViewModel that updates a counter on a XAML page but can't figure out what I'm doing wrong...

The initial value of itemCount is well displayed but after each increment it is not updated.

Here is the XAML source code :

<?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:maps="clr-namespace:Xamarin.Forms.Maps;assembly=Xamarin.Forms.Maps"
             xmlns:local="clr-namespace:XamarinFormsTest01"
             mc:Ignorable="d"
             x:Class="XamarinFormsTest01.MainPage">

    <StackLayout>
        <Label x:Name="lblMain" Text="{Binding ItemCount}" 
           HorizontalOptions="Center"
           VerticalOptions="CenterAndExpand" >
            <Label.BindingContext>
                <local:ItemCountViewModel />
            </Label.BindingContext>
        </Label>        
        <Button x:Name="BtnStart" Text="start" Pressed="BtnStart_Pressed" />
        <Button x:Name="BtnStop" Text="stop" Pressed="BtnStop_Pressed" />
    </StackLayout>
</ContentPage>

And the ViewModel source code :

public class ItemCountViewModel : INotifyPropertyChanged
{
    private static ItemCountViewModel instance = new ItemCountViewModel();
    public static ItemCountViewModel GetInstance()
    {
        return instance;
    }

    int itemCount;
    public event PropertyChangedEventHandler PropertyChanged = delegate { };
    public ItemCountViewModel()
    {
        itemCount = 0;
        Device.StartTimer(TimeSpan.FromSeconds(1), () =>
        {
            itemCount++;
            return true;
        }
        );
    }
    public int ItemCount
    {
        set
        {
            itemCount = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ItemCount"));
        }
        get
        {
            return itemCount;
        }
    }
}
KotlinIsland
  • 799
  • 1
  • 6
  • 25
  • Maybe take a look at the following answer https://stackoverflow.com/a/56487270/8293694. Might help you out. – vasilisdmr Mar 10 '20 at 14:34

1 Answers1

2

From ItemCount.set you are raising PropertyChanged, which is required for the view to update according to the state of the viewmodel. Otherwise the view would have to poll for state changes, which would be a waste of resources, especially on mobile devices where we'd like to avoid draining the battery by overusing the processor.

Anyway, when setting itemCount directly as you are doing in

Device.StartTimer(TimeSpan.FromSeconds(1), () =>
{
    itemCount++;
    return true;
}

ItemCount.set is never called, hence PropertyChanged is never raised and the view has got no chance to determine that the viewmodel changed its state. Furthermore I guess that ItemCount has to be set in the UIs main thread, hence you have to wrap the call to ItemCount.set by Device.BeginInvokeOnMainThread (see the documentation)

Change the code to

Device.StartTimer(TimeSpan.FromSeconds(1), () =>
{
    Device.BeginInvokeOnMainThread (() => 
    {
      ItemCount++;
    });
    return true;
}

and the view should update.

Paul Kertscher
  • 9,416
  • 5
  • 32
  • 57
  • I have now another problem. On another page (MainPage.xaml.cs), when I do the following call, then the ItemCount is not updated : ItemCountViewModel.GetInstance().ItemCount++; – KotlinIsland Mar 10 '20 at 14:57
  • 1
    @KotlinIsland I'm sorry, but I don't understand what you mean by that. Do you expect the `ItemCount` to persist between pages? – Paul Kertscher Mar 10 '20 at 15:00
  • When I do "ItemCount++" from inside the ViewModel then the value is well updated. But when I increment ItemCount from the XAML code-behind file then it does not update... – KotlinIsland Mar 10 '20 at 15:02
  • 1
    @KotlinIsland Maybe you should pose another question for that by adding the respective code. – Paul Kertscher Mar 10 '20 at 15:04