KotlinIsland
KotlinIsland

Reputation: 859

ViewModel not updating form (Xamarin.Forms)

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;
        }
    }
}

Upvotes: 0

Views: 972

Answers (1)

Paul Kertscher
Paul Kertscher

Reputation: 9723

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.

Upvotes: 2

Related Questions