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>
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
}
}
TripTotal
has changed ? – Greggz