James
James

Reputation: 13

ViewModel model Member is not updated in the UI after OnPropertyChanged

I am navigating to a detail page, passing Id as a parameter, retrieving it and looking up the detail model from a service. That part works fine. However, in the page the model object within the ViewModel is not updated even though it is ObservableProperty.

Defined DI:

CreateRoutePageMapping<MembersPage, MembersViewModel>(),
CreateRoutePageMapping<MemberPage, MemberViewModel>(),

Defined model:

public partial class MemberModel : ObservableObject
{
    [ObservableProperty]
    private string firstName;

    [ObservableProperty]
    private string lastName;

    public MemberModel()
    {
        FirstName = String.Empty;
        LastName = String.Empty;
    }
}

Defined ViewModel:

public partial class MemberViewModel : BaseViewModel, IQueryAttributable
{
    readonly IMembersService _membersService;
    readonly IStringLocalizer _localizer;

    [ObservableProperty]
    int id;

    [ObservableProperty]
    MemberModel member = new();

    partial void OnIdChanged(int value)
    {
        Task.Run(async () => await GetMemberAsync(id: Id));
    }

    public MemberViewModel(
        IMembersService membersService,
        IConnectivity connectivity,
        IDialogService dialogService,
        IStringLocalizer<ProjectStrings> localizer
        ) : base(connectivity, dialogService, localizer)
    {
        _membersService = membersService;
        _localizer = localizer;
    }

    [RelayCommand]
    async Task GetMemberAsync(int id)
    {
        if (IsBusy)
            return;

        try
        {
            if (!(await HasInternetAccessAsync()))
                return;

            IsBusy = true;

            Member = await _membersService.GetMemberAsync(id: id);
            //OnPropertyChanged(nameof(Member));
            OnPropertyChanged(nameof(Member.FirstName));
            OnPropertyChanged(nameof(Member.LastName));
        }

        catch (Exception ex)
        {
            Debug.WriteLine($"Error retrieving member: {ex.Message}");
        }

        finally
        {
            IsBusy = false;
        }
    }

    void IQueryAttributable.ApplyQueryAttributes(IDictionary<string, object> query)
    {
        int id = (int)query["id"];
        Id = id;
        OnPropertyChanged(nameof(Id));
    }
}

Defined Page:

using static CommunityToolkit.Maui.Markup.GridRowsColumns;

public partial class MemberPage : BaseContentPage<MemberViewModel>
{
    public MemberPage(MemberViewModel memberViewModel) :
        base(viewModel: memberViewModel, pageTitle: memberViewModel.Title)
    {
        Style = AppStyles.ContentPageStyle;

        Content = new ScrollView()
        {
            Content = new VerticalStackLayout()
            {
                VerticalOptions = LayoutOptions.Start,
                HorizontalOptions = LayoutOptions.Center,

                Children =
                {
                    new Label()
                    {
                    }
                    .Bind(Label.TextProperty, 
                        static (MemberViewModel vm) => vm.Member.FirstName, mode: BindingMode.OneWay),

                    new Label()
                    {
                    }
                    .Bind(Label.TextProperty,
                        static (MemberViewModel vm) => vm.Member.LastName, mode: BindingMode.OneWay)
                }
            }
        };
    }
}

In the ViewModel, if this line is enabled: //OnPropertyChanged(nameof(Member)); then the updates are not reflected in the UI.

However, if I specify the attributes in the model explicitly:

OnPropertyChanged(nameof(Member.FirstName));
OnPropertyChanged(nameof(Member.LastName));

then they are reflected in the UI. Why does the OnPropertyChanged(nameof(Member)) not work?

Upvotes: 0

Views: 362

Answers (2)

James
James

Reputation: 13

I was able to get this to work using this binding from the community toolkit examples. It is verbose but avoids the explicit OnPropertyChanged.

                    new Label()
                {
                }
                .Bind(
                    targetProperty: Label.TextProperty,
                    getter: static (MemberViewModel vm) => vm.Member.FirstName,
                        handlers: new (Func<MemberViewModel, object?>, string)[]
                         {
                            (vm => vm, nameof(MemberViewModel.Member)),
                            (vm => vm.Member, nameof(MemberViewModel.Member.FirstName)),
                         },
                    setter: static (MemberViewModel vm, string text) => vm.Member.FirstName = text),

Upvotes: 0

Jessie Zhang -MSFT
Jessie Zhang -MSFT

Reputation: 13879

However, if I specify the attributes in the model explicitly: OnPropertyChanged(nameof(Member.FirstName)); OnPropertyChanged(nameof(Member.LastName)); then they are reflected in the UI. Why does the OnPropertyChanged(nameof(Member)) not work?

From the code you shared, I couldn't reproduce this problem.

But you can first recheck the bind way you used.

You can refer to the following code:

    <Label  Text="{Binding Member.FirstName}"/>

    <Label  Text="{Binding Member.LastName}"/>

Then we don't need call OnPropertyChanged, we can just do as follows.

Once the value of Member changes, the UI will refresh itself.

try
    {
        if (!(await HasInternetAccessAsync()))
            return;

        IsBusy = true;

        Member = await _membersService.GetMemberAsync(id: id);

        //OnPropertyChanged(nameof(Member));
        //OnPropertyChanged(nameof(Member.FirstName));
        //OnPropertyChanged(nameof(Member.LastName));
    }

Upvotes: 0

Related Questions