1
votes

One Property [TripTotal] on my ContentPage in Xamarin Forms is bound to label On screen is not updating. I went to property setter and found that it is 'Notifying'See the Console.WriteLine($"{TripTotal}") statement below It does Update OneTime at Startup. to $99.0 What is the issue here?

From Application output

TripTotal 100

TripTotal 101

TripTotal 102

TripTotal 103

TripTotal 104

TripTotal 105

TripTotal 106

TripTotal 107

TripTotal 108

TripTotal 109

TripTotal 110



    <?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"
             mc:Ignorable="d" x:Class="TripCalculator.Views.ExpensePage"
             Title="{Binding Title}" x:Name="BrowseItemsPage">
    <ContentPage.ToolbarItems>
        <ToolbarItem Text="Add" Clicked="AddItem_Clicked" />
    </ContentPage.ToolbarItems>
    <StackLayout Padding="10">
        <StackLayout Orientation="Horizontal" >
            <Label Text="TripTotal:" FontSize="Title"/>
            <Label Text="{Binding TripTotal, Mode=TwoWay, StringFormat ='${0:F2}'}" x:Name="trip_total" FontSize="Title" HorizontalOptions="End"/>
            <Label Text="{Binding TripTotal_str, Mode=TwoWay}"  FontSize="Title" HorizontalOptions="End"/>
        </StackLayout>
        <BoxView BackgroundColor="Black" HeightRequest="2" WidthRequest="500"/>
        <ListView x:Name="ItemsListView"
                  ItemsSource="{Binding SubTotals}"
                  VerticalOptions="FillAndExpand"
                  HasUnevenRows="true"
                  RefreshCommand="{Binding LoadItemsCommand}"
                  IsPullToRefreshEnabled="true"
                  IsRefreshing="{Binding IsBusy, Mode=OneWay}"
                  CachingStrategy="RecycleElement"
                  ItemSelected="OnItemSelected">  
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <StackLayout Padding="10">
                            <Label Text="{Binding StudentName}"
                                   LineBreakMode="NoWrap"
                                   Style="{DynamicResource ListItemTextStyle}"
                                   FontSize="Subtitle" />
                            <Label Text="{Binding Amount, StringFormat ='${0:F2}'}"       
                                   Style="{DynamicResource ListItemDetailTextStyle}"
                                   FontSize="Subtitle"  />
                        </StackLayout>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </StackLayout>
</ContentPage>

enter image description here

Here is ViewModel

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using TripCalculator.Models;
using Newtonsoft.Json;
using Xamarin.Forms;
using System.Windows.Input;
using TripCalculator.Helpers;
using TripCalculator.Views;

namespace TripCalculator.ViewModels
{
    public class ExpenseVM : BaseVM
    {
        public ExpenseVM()
        {
            Title = "Students";
            Expenses = new ObservableCollection<Expense>();
            Messages = new ObservableCollection<string>();
            SubTotals = new ObservableCollection<SubTotal>();
            StudentTotals = new ObservableDictionary<string, double>();
            Expenses.CollectionChanged += (s, e) => TripTotal += 1;
            MessagingCenter.Subscribe<NewExpensePage, Expense>
                (this, "AddItem", async (obj, item) =>
            {
                var newItem = item as Expense;

                await DataSource.AddItemAsync(newItem);

                await this.ExecuteLoadItemsCommand();
            });

        }

        #region OBSERVABLES
        public ICommand LoadItemsCommand
        {
            get
            {
                return new Command (async() => await ExecuteLoadItemsCommand());
            }
        }

        public ICommand UpdateItemCommand
        {
            get
            {
                return new Command<object>(async (id) => await ExecuteUpdateItemCommand(id));
            }
        }

        public Command DeleteCommand
        {
            get
            {
                return new Command (async () => await ExecuteDeleteCommand());
            }
        }

        private Expense selectedItem;
        public Expense SelectedItem {   get => selectedItem;
                                        set => SetProperty(ref selectedItem, value); }
        private ObservableCollection<Expense> expenses;
        public ObservableCollection<Expense> Expenses
        {
            get => expenses;
            set => SetProperty(ref expenses, value);
        }

        private ObservableCollection<SubTotal> subtotals;
        public ObservableCollection<SubTotal> SubTotals
        {
            get => subtotals;
            set => SetProperty(ref subtotals, value);
        }

        public ObservableCollection<string> Students { get { return QueryDistinctNames(); } }

        public ObservableCollection<string> Messages; 

        private ObservableDictionary<string, double> studentTotals;
        public ObservableDictionary<string, double> StudentTotals
        {
            get => studentTotals;
            internal set => SetProperty(ref studentTotals, value);
        }

        double tripTotal = 99;
        public double TripTotal
        {
            get => tripTotal;
            set => SetProperty(ref tripTotal, value);

        }

        public List<string> ExpenseIds { get { return queryAllExpenseIds(); } }

        private void reQueryStudents()
        {
            var studList = Students;
            foreach (string student in studList)
            {
                studentTotals[student] = QueryStudentTotal(student);
            }
            //set Notification cyccle
            this.SubTotals.Clear();
            foreach (var k in studentTotals.Keys)
            {
                subtotals.Add(new SubTotal { Amount = studentTotals[k], StudentName = k });

            }
            //TripTotal = QueryTotal();
            SubTotals = subtotals;

            StudentTotals = studentTotals;

        }

        public double QueryStudentTotal(string student)
        {
            return (from exp in Expenses select exp)
                                .Where(e => e.StudentName == student)
                                .Sum(e => e.Amount);
        }

        internal List<string> queryAllExpenseIds()
        {
            var lst = Expenses.ToList<Expense>();
            // Left As Distinct() although there should be no redundant Id's
            return lst.Select(x => x.Id).Distinct().ToList();
        }

        private double QueryTotal()
        {
            return (from exp in Expenses select exp).Sum(e => e.Amount);
        }

        private ObservableCollection<string> QueryDistinctNames()
        {
            var lst = Expenses.ToList<Expense>();
            List<string> myStudents = lst.Select(x => x.StudentName).Distinct().ToList();
            return myStudents.ToObservableCollection<string>();

        }
        #endregion

        #region CMDS_AND_DATASTORE_OPS
        async Task ExecuteLoadItemsCommand()
        {
            if (IsBusy)
                return;

            IsBusy = true;

            try
            {
                Expenses.Clear();
                if (DataSource != null)
                {
                    var items = await DataSource.GetItemsAsync(true);
                    double sum = 0;
                    foreach (var item in items)
                    {
                        Expenses.Add(item);
                        sum += item.Amount;
                    }
                    reQueryStudents();
                    tripTotal = sum;
                    OnPropertyChanged(nameof(TripTotal));
                }else
                { Console.WriteLine("null Data Store"); }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex);
            }
            finally
            {
                IsBusy = false;
            }
        }

        async Task ExecuteUpdateItemCommand(object newAmt)
        {
            if (IsBusy)
                return;

            IsBusy = true;
            try
            {
                double newCost = (double)newAmt;

                Expense newExpense = new Expense
                {
                    Amount = newCost,
                    StudentName = SelectedItem.StudentName
                };


                var didUpdate = await DataSource.UpdateItemAsync(SelectedItem, newExpense);
                if (didUpdate)
                {  
                    await ExecuteLoadItemsCommand();
                }    
            }
            catch
            {

            }
        }

        private async Task ExecuteDeleteCommand()
        {
            await DataSource.DeleteItemAsync(SelectedItem.Id);
            await ExecuteLoadItemsCommand();
        }
        #endregion

        #region UTIL
        public override string ToString()
        {
            var serializedItem = JsonConvert.SerializeObject(Expenses);
            return serializedItem;
        }

        public void FromString(string json)
        {
            // Hold your hat a bunch of conversions ahead :)
            Expenses = JsonConvert.DeserializeObject<IEnumerable<Expense>>(json)
                                   .ToList()
                                   .ToObservableCollection<Expense>();

        }
        #endregion
    }
}

Here is BaseViewModel

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;

using TripCalculator.Models;
using TripCalculator.Services;

namespace TripCalculator.ViewModels
{
    public class BaseVM
    {
        protected static IDataSource<Expense> DataSource
        {
            get { return App.DataSource;  }
        }

        bool isBusy = false;
        public bool IsBusy
        {
            get { return isBusy; }
            set { SetProperty(ref isBusy, value); }
        }

        string title = string.Empty;
        public string Title
        {
            get { return title; }
            set { SetProperty(ref title, value); }
        }

        #region INotifyPropertyChanged

        protected bool SetProperty<T>(ref T backingStore, T value,
            [CallerMemberName]string propertyName = "",
            Action onChanged = null)
        {
            if (EqualityComparer<T>.Default.Equals(backingStore, value))
                return false;

            backingStore = value;
            onChanged?.Invoke();
            OnPropertyChanged(propertyName);
            if (propertyName=="TripTotal")
            {
                Console.WriteLine($"TripTotal {value}");
            }
            return true;
        }

        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
        {
            var changed = PropertyChanged;
            if (changed == null)
                return;

            changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
        #endregion
    }
}
2
Can you also include ViewModel code?shanranm
Have you raised the event in your ViewModel that the TripTotal has changed ?Greggz
At Greggz yes that TripTotal output to Console is from the SetterMark Wardell
@shanranm I posted Vm and BaseVM. there are dependencies but you can see the statements if (propertyName=="TripTotal") { Console.WriteLine($"TripTotal {value}"); }Mark Wardell

2 Answers

1
votes

In the ExecuteLoadItemsCommand() you need to do this TripTotal = sum; instead of this tripTotal = sum;

1
votes

Oh silly me! I found it. I only had to Add the INotifyPropertyChanged to BaseVM. Yes I had a working implementation but had not told Xamarin I was sending Notifications. I changed nothing else. Programmer life. Oh and the other fields were updating because they were bound to ObservableCollecitons of