XAML binding to model not updating UI Maui Monkeys example (dotnet-maui-workshop)

Dave M 21 Reputation points
2022-08-17T18:42:12.153+00:00

I added a text entry to Monkeys[0].Name (for the baboon) and I can change the .Name and it updates the codebehind and if I navigate to the Baboon details page it has the new Name, but the Monkeys page never updates the name of the baboon. What do I need to change to get this binding to work properly?

Here is my slight modification:

   <CollectionView.ItemTemplate>  
                   <DataTemplate x:DataType="model:Monkey">  
                       <Grid Padding="10">  
                           <Frame HeightRequest="125" Style="{StaticResource CardView}">  
                               <Frame.GestureRecognizers>  
                                   <TapGestureRecognizer Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:MonkeysViewModel}}, Path=GoToDetailsCommand}" CommandParameter="{Binding .}" />  
                               </Frame.GestureRecognizers>  
                               <Grid Padding="0" ColumnDefinitions="125,*">  
                                   <Image  
                                       Aspect="AspectFill"  
                                       HeightRequest="125"  
                                       Source="{Binding Image}"  
                                       WidthRequest="125" />  
                                   <VerticalStackLayout Grid.Column="1" Padding="10">  
                                       <Label Style="{StaticResource LargeLabel}" Text="{Binding Name, Mode=TwoWay}" />         
                                       <Label Style="{StaticResource MediumLabel}" Text="{Binding Location}" />  
                                   </VerticalStackLayout>  
                               </Grid>  
                           </Frame>  
                       </Grid>  
                   </DataTemplate>  
               </CollectionView.ItemTemplate>  
           </CollectionView>  
     
    <!--  MY ENTRY ADDITIONS   I ALSO PUT TWOWAY FOR THE DATATTEMPLATE ABOVE  -->  
      <VerticalStackLayout Grid.Row="0" Grid.Column="2" >  
               <Label Text="NewBaboonName" Margin ="0,10,0,0" HeightRequest="20" WidthRequest="200" VerticalOptions="End" />  
               <Entry Text="{Binding Monkeys[0].Name, Mode=TwoWay}" Margin ="2" HeightRequest="50" WidthRequest="200" Placeholder="ChangeName" />  
           </VerticalStackLayout>**  
      

The Monkeys page passes the details page the Model. But it is more that the Monkeys[0].Name does not update on the Monkeys page. The whole application is by james montemagno. It can be downloaded at https://github.com/dotnet-presentations/dotnet-maui-workshop (Use Part 6 for the final code).
All I did was check the binding by adding the following line to the MainPage.xaml , which is bound to viewmodel:MonkeysViewModel.
<Entry Text="{Binding Monkeys[0].Name, Mode=TwoWay}" Margin ="2" HeightRequest="50" WidthRequest="200" Placeholder="ChangeName" />

I just find the binding weird in that it does update the Monkeys[0].Name on the Monkeys detail page, but not on the monekys page(MainPage). Another bizarre "bug" is that if you change the Mode on the entry from TwoWay to OneWay, or OneWay to TwoWay the Monkeys[0].Name field will update on the monkeys page(MainPage).

I'm not sure if the binding is in "developement" mode or this is the way it should work. Though I would think if the value in the object is updated in the code behind it should update on the UI (with TwoWay at least).

Copy of the MonkeysViewModel:

using MonkeyFinder.Services;  
  
namespace MonkeyFinder.ViewModel;  
  
public partial class MonkeysViewModel : BaseViewModel  
{  
    public ObservableCollection<Monkey> Monkeys { get; } = new();  
    MonkeyService monkeyService;  
    IConnectivity connectivity;  
    IGeolocation geolocation;  
    public MonkeysViewModel(MonkeyService monkeyService, IConnectivity connectivity, IGeolocation geolocation)  
    {  
        Title = "Monkey Finder";  
        this.monkeyService = monkeyService;  
        this.connectivity = connectivity;  
        this.geolocation = geolocation;  
    }  
  
    [ObservableProperty]  
    bool isRefreshing;  
  
    [RelayCommand]  
    async Task GetMonkeysAsync()  
    {  
        if (IsBusy)  
            return;  
  
        try  
        {  
            if (connectivity.NetworkAccess != NetworkAccess.Internet)  
            {  
                await Shell.Current.DisplayAlert("No connectivity!",  
                    $"Please check internet and try again.", "OK");  
                return;  
            }  
  
            IsBusy = true;  
            var monkeys = await monkeyService.GetMonkeys();  
  
            if(Monkeys.Count != 0)  
                Monkeys.Clear();  
                  
            foreach(var monkey in monkeys)  
                Monkeys.Add(monkey);  
  
        }  
        catch (Exception ex)  
        {  
            Debug.WriteLine($"Unable to get monkeys: {ex.Message}");  
            await Shell.Current.DisplayAlert("Error!", ex.Message, "OK");  
        }  
        finally  
        {  
            IsBusy = false;  
            IsRefreshing = false;  
        }  
  
    }  
      
    [RelayCommand]  
    async Task GoToDetails(Monkey monkey)  
    {  
        if (monkey == null)  
        return;  
  
        await Shell.Current.GoToAsync(nameof(DetailsPage), true, new Dictionary<string, object>  
        {  
            {"Monkey", monkey }  
        });  
    }  
  
    [RelayCommand]  
    async Task GetClosestMonkey()  
    {  
        if (IsBusy || Monkeys.Count == 0)  
            return;  
  
        try  
        {  
            // Get cached location, else get real location.  
            var location = await geolocation.GetLastKnownLocationAsync();  
            if (location == null)  
            {  
                location = await geolocation.GetLocationAsync(new GeolocationRequest  
                {  
                    DesiredAccuracy = GeolocationAccuracy.Medium,  
                    Timeout = TimeSpan.FromSeconds(30)  
                });  
            }  
  
            // Find closest monkey to us  
            var first = Monkeys.OrderBy(m => location.CalculateDistance(  
                new Location(m.Latitude, m.Longitude), DistanceUnits.Miles))  
                .FirstOrDefault();  
  
            await Shell.Current.DisplayAlert("", first.Name + " " +  
                first.Location, "OK");  
  
        }  
        catch (Exception ex)  
        {  
            Debug.WriteLine($"Unable to query location: {ex.Message}");  
            await Shell.Current.DisplayAlert("Error!", ex.Message, "OK");  
        }  
    }  
}  
.NET MAUI
.NET MAUI
A Microsoft open-source framework for building native device applications spanning mobile, tablet, and desktop.
2,920 questions
{count} votes

Accepted answer
  1. Rob Caplan - MSFT 5,422 Reputation points Microsoft Employee
    2022-08-19T19:59:16.853+00:00

    This is expected behavior. The Monkey object is not a BindableObject and doesn't raise notifications when its properties are changed. Once it's been initially bound further changes won't reflect in the existing binding.

    Your ObservableCollection<Monkey> Monkeys collection is observable. The ObservableCollection will raise change notifications as Monkeys are added to and from the collection.

    Changes pushed to the Monkeys[0] object should go through to the backing data (you can confirm in the debugger), and they'll be present in new renditions of the Monkeys class (e.g. if a new details page is instantiated) but won't be reflected on existing objects that were created from the Monkey object before it was changed.

    If you look at the Monkey's definition you can see that it is a plain old CLR object and doesn't implement INotifyPropertyChanged either directly or by inheriting from BindableObject.

    https://github.com/dotnet-presentations/dotnet-maui-workshop/blob/main/Part%206%20-%20AppThemes/MonkeyFinder/Model/Monkey.cs
    namespace MonkeyFinder.Model;

    public class Monkey  
    {  
        public string Name { get; set; }  
        public string Location { get; set; }  
        public string Details { get; set; }  
        public string Image { get; set; }  
        public int Population { get; set; }  
        public double Latitude { get; set; }  
        public double Longitude { get; set; }  
    }  
    
    0 comments No comments

0 additional answers

Sort by: Most helpful