简体   繁体   中英

Property not updating after service completes

I'm trying to get my head around data binding in Xamarin.Forms . I've read lots of the guides and played with some examples and I am now trying to implement some of my own basic binding.

I've got a Strings file in which I've declared an empty variable:

public static class Strings
{
    public static string UserDisplayName;
}

On load of my View, it runs an async function to grab data from a Azure SQL DB which then populates the string

Strings.UserDisplayName = user.FirstName;

In my view page I've bound a label to a variable userDisplayNm

<Label Text="{Binding UserDisplayNm}"></Label>

In my ViewModel I have the following to set UserDisplayNm, however it only ever returns "Welcome, ". How do i get it to fire this again after the sync service has completed & the Strings.UserDisplayname value changes? I think I'm missing a link to an PropertyChanged event or something?

    namespace Travel_Comp.ViewModels
    {
        public sealed class MenuViewModel : INotifyPropertyChanged
        {
        public event PropertyChangedEventHandler PropertyChanged;

        public MenuViewModel()
        {
            this.UserDisplayNm = Strings.UserDisplayName;
        }

        public string UserDisplayNm
        {
            set
            {
                if (Strings.UserDisplayName != value)
                {
                    value = Strings.UserDisplayName;

                    if (PropertyChanged != null)
                    {
                        PropertyChanged(this, new PropertyChangedEventArgs("UserDisplayNm"));
                    }
                }
            }
            get
            {
                return "Welcome, " + Strings.UserDisplayName;
            }
        }

    }
}

EDIT:

Thanks for your replies. I think I'm getting closer based on the replies below, here is what I've now got, although The MenuViewModel.LoadAsync() is throwing an error "Inaccessible due to its protection level", so i can't compile to check it yet. Is this what you were suggesting & any ideas on the Protection level issue??

Strings file:

public static class Strings
{
    public static string UserDisplayName;
}

ViewModel:

namespace Travel_Comp.ViewModels
{
    public sealed class MenuViewModel : INotifyPropertyChanged
    {
        //Azure sync process
        ServerManager manager;
        public event PropertyChangedEventHandler PropertyChanged;


        public MenuViewModel()
        {
            //Initial set of UserDisplayNm 
            this.UserDisplayNm = Strings.UserDisplayName;
        }

        async void LoadAsync()
        {
            try
            {
                //Run process to populate Strings.UserDisplayNm, set Syncitems to true to sync with Server
                foreach (var user in await manager.GetUsersAsync(syncItems: true))
                {
                    Strings.UserDisplayName = user.FirstName;
                }
            }
            catch (Exception e)
            {
                Console.WriteLine($"Error while retrieving user name: {e}");
            }
        }

        public string UserDisplayNm
        {
            set
            {
                if (Strings.UserDisplayName != value)
                {
                    value = Strings.UserDisplayName;

                    if (PropertyChanged != null)
                    {
                        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(UserDisplayNm)));
                    }
                }
            }
            get
            {
                return "Welcome, " + Strings.UserDisplayName;
            }
        }

    }
}

View:

protected override async void OnAppearing()
{
    base.OnAppearing();

    ViewModels.MenuViewModel.LoadAsync();

}

So if you're looking some guidance for MVVM, you should know that usually you put your dependencies in your view model constructor, here your Azure service.

Also you could use a existing MVVM framework that will make things easy for you, like Prism or FreshMVVM .

But if you want to go for full vanilla you can also call your vm code from the view code behind.

So I'm suggesting this modification to your MenuViewModel :

private IAzureService _azureService;
private string _userDisplayNm;

public MenuViewModel(IAzureService azureService)
{
    _azureService = azureService;
}

public string UserDisplayNm
{
    get
    {
        return _userDisplayNm;
    }
    set
    {
        if (_userDisplayNm != value)
        {
            _userDisplayNm = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(UserDisplayNm)));
        }
    }
}

public async void LoadAsync()
{
    try
    {
        UserDisplayNm = await _azureService.GetUserName();
    }
    catch (Exception exception)
    {
        Debug.WriteLine($"Error while retrieving user name: {exception}")
    }
}

Then in you view code behind:

void OnAppearing()
{
    _menuViewModel.LoadAsync();
}

To resolve the question: Inaccessible due to its protection level , you can try to add the public access modifier before the function of LoadAsync .

public async void LoadAsync(){
   //....
  }

And I have created a simple demo to simulate your code.

The main code is:

 public sealed class TestModel: INotifyPropertyChanged
{

 //*******************************************
    string _userDisplayName;

    public string UserDisplayName {
        set { SetProperty(ref _userDisplayName, value); }

        get { return _userDisplayName; }
    }

    public async void LoadAsync()
    {
        try
        {
            UserDisplayName = "updated value: Angela";

            Strings.UserDisplayName = UserDisplayName;
        }
        catch (Exception exception)
        {
            Debug.WriteLine($"Error while retrieving user name: {exception}");
        }
    }


    bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
    {
        if (Object.Equals(storage, value))
            return false;

        storage = value;
        OnPropertyChanged(propertyName);
        return true;
    }

    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

xaml

 <Label Text="{Binding UserDisplayName }" BackgroundColor="Yellow" 
        VerticalOptions="Center" HorizontalOptions="Fill"  HeightRequest="50" />
 <Button  Text="update the Label value"  Clicked="Button_Clicked"/>

And use like this:

public partial class MyPage1 : ContentPage
{
    TestModel model;

    public MyPage1 ()
    {
        InitializeComponent ();

        model = new TestModel();
        BindingContext = model;       
    }

    private void Button_Clicked(object sender, EventArgs e)
    {
        model.LoadAsync();
    }
}

The effect is: 在此处输入图片说明

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM