kdhansen
kdhansen

Reputation: 117

View doesn't get updated by Model

I apologize for this lengthy question but I hope you can stick with me. I have tried to do my best to include all the relevant code snippets in order to explain exactly what I want to achieve.

I am trying to convert my mobile application to the MVVM design pattern and I am experiencing a little difficulty with updating my View from the changes happening in my Model.

I have split my application into a classic Model, View, View Model structure.

Currently, I am trying to propagate data from my Model SoundScapeData.cs to my View HomePage.xaml.

My HomePage.xaml code-behind file looks like this:

namespace AX2018
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class HomePage : ContentPage
    {
        public HomePage()
        {
            InitializeComponent();
            BindingContext = new HomePageViewModel();
        }

        private void OnConnectButtonClicked(object sender, EventArgs e)
        {
            Bluetooth bluetooth = new Bluetooth();
            bluetooth.Connect();
        }
    }
}

As you can see, I bind my data using the BindingContext keyword and bind it to a new instance of the HomePageViewModel class.

I am connecting to a Bluetooth device, therefore the call to bluetooth.Connect() which invokes upon hitting a Button in the HomePage.xaml View. This Bluetooth device then updates the app with certain values.

I want to emphasize that the Bluetooth connection is working well, and have been verified to work with the View prior to converting to the MVVM design pattern.

My ViewModel, HomePageViewModel.cs, looks like this:

namespace AX2018
{
    public class HomePageViewModel
    {
        private SoundScapeData _soundScapeData;

        public SoundScapeData SoundScapedata { get { return _soundScapeData; } }

        public HomePageViewModel()
        {
            _soundScapeData = new SoundScapeData();
        }
    }
}

On a side note, I am a little confused how to design the View Model, since the Model SoundScapeData.cs currently is so simple as is. Is this intermediary View Model even necessary?

This is my SoundScapeData.cs Model:

namespace AX2018
{
    public class SoundScapeData : INotifyPropertyChanged
    {
        private double _spl;

        public double SPL
        {
            get { return _spl; }
            set
            {
                _spl = value;
                OnPropertyChanged();
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

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

As you can see from the Model, it implements the INotifyPropertyChanged interface, and has the relevant function to handle the event in the method OnPropertyChanged().

My View, HomePage.xaml, is rather lengthy - it consists of a 9 x 3 grid, with labels placed in certain positions. These labels should reflect values in the SoundScapeData.cs Model (and other models, but that is down the road). Here are the relevant snippets:

<?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:local="clr-namespace:AX2018"
         x:Class="AX2018.HomePage">

<ContentPage.BindingContext>
    <local:HomePageViewModel/>
</ContentPage.BindingContext>

...

<Label Text="{Binding SoundScapedata.SPL}" 
FontSize="68" Style="{StaticResource ColoredLabel}" 
Grid.Row="4" 
Grid.Column="1" 
Margin="-20"></Label>

As you can see, I have bound the data in the View HomePage.xaml to the View Model HomePageViewModel.cs, which holds an instance of the SoundScapeData.cs Model, which in turn implements the INotifyPropertyChanged interface.

To my understanding, this is the proper approach in terms of data binding when using the MVVM design pattern. However, the changes in SPL are not reflected in my View HomePage.xaml.

I update the SPL value in a separate class, Bluetooth.cs, which inherits from the SoundScapeData.cs Model. Here's a snippet from the Connect() method from the Bluetooth class:

namespace AX2018
{
    public async void Connect()
    {
        public class Bluetooth : SoundScapeData
        ...
        var dbValue = (double)BitConverter.ToUInt16(temp1, 0) / 256 * 48;
        SPL = dbValue;
        ...
    }
}

Again, I apologize for this rather long question, but I hope someone out there can point out to me what I am doing wrong. I want to reiterate that the SPL value did update in the View prior to converting to MVVM, so I am definitely doing something wrong with my data binding.

Thank you in advance,

kdhansen

Upvotes: 2

Views: 131

Answers (2)

Javi Quir&#243;s
Javi Quir&#243;s

Reputation: 49

Your problem is that you are changing the instance of the model and lose the binding of the view. The response of Juan Carlos is correct, you are not applying pure MVVM, the view should not know the model, the viewmodel is responsible for adapting the model to the view. The OnConnectButtonClicked that you have in the code behind of the view does not apply the MVVM pattern either, you should use Commands. The link http://www.learnmvvm.com/ is a good starting point.

If you upload an example project of your code to some repository, I'll modify it by applying MVVM and SOLID principles.

Upvotes: 0

Juan Carlos Rodriguez
Juan Carlos Rodriguez

Reputation: 784

I will answer here because comments are too short to explain. If I mispell some names excuse me :P Let's summarize your scenario:

  • HomePage is your View
  • HomePageViewModel is your VM
  • SoundScapeData is your Model

Now:

HomePage (view)

I dont really know if in Xamarin oyu have to duplicate this, remember you set it in your code behind.

<ContentPage.BindingContext>
<local:HomePageViewModel/>
</ContentPage.BindingContext>

You should change this too:

<Label Text="{Binding Spl}" <!--You dont need SoundScapeData anymore this is the public VM property -->
FontSize="68" Style="{StaticResource ColoredLabel}" 
Grid.Row="4" 
Grid.Column="1" 
Margin="-20"></Label>

HomePageViewModel

It should have as many properties as you need. Let's say your model has just one property SPL . Thats your model now you have to put it into the View via your VM. So your VM should have a property (public/private) to adapt it to the View plus your Model.

private string spl;
public string Spl
       {
         get {return this.spl;}
         set
            {
            if (this.spl != value)
            {
            this.spl = value;
            OnPropertyChanged("SPL);
            }

When you click the button and you connect to the bluetooth or whatever it does (;P) as it changes the model you have to change your VM properties. Remember, your model has to be the same instance you have in your VM.

So... you should attach to the Model property changed so you change your VM properties and the best way to do that is creating a class (let's try to apply SOLID as much as we can)

public class YourNewDataSource
{
    #region Attributes

    private readonly HomePageViewModel homePageViewModel;

    #endregion

    #region Public Methods


    public YourNewDataSource(HomePageViewModel homePageViewModel)
    {
      this.homePageViewModel = homePageViewModel;
    }

    public void Initialize()
    {
        this.homePageViewModel.SoundScapeData.PropertyChanged += this.OnHomePageModelPropertyChanged;
    }

    #endregion

    #region Event Handlers

    private void OnHomePageModelPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        switch (e.PropertyName)
        {
            case "SPL":
                this.homePageViewModel.Spl = this.homePageViewModel.SoundScapeData.Spl;
                break;
        }
    }

    #endregion
}

Now when you start your application or you want to show your View you have to create a new YourNewDataSource with your VM:

public HomePage()
    {
        InitializeComponent();
        HomePageViewModel homePageViewModel = new HomePageViewModel();
        YourNewDataSource yourNewDataSource = new YourNewDataSource(homePageViewModel)
        BindingContext = homePageViewModel;
    }

Take a look, try it and ask if you don't get some s*** I just wrote here :D


I have just took a look to your bluetooth.Connect .

I don't know if it is mandatory for the Bluetooth class to inherit from SoundScapeData but right now it won't work cause you lose the VM's model when you connect. If you don't really need to inherit from SoundScapeData just add a parameter to your connect method and pass the Vm.SoundScapeData to it.

Upvotes: 1

Related Questions