3

I'm making a Xamarin.Forms MVVM App with an Entity Framework Core SQLite to save a Db locally in the mobile phone.

I generate the Db and some initial items, later through App I add a new item, for the moment it works fine (Refreshing the listview in App and showing all items correctly).

The problem is when I want update one item in the Db. I update the item but doesn't update in the listview. The only way that it updates in the listview is restarting de App.

How can I refresh the listview with the updated item?

This is the Datacontext code:

public class DataContext : DbContext
{
    public DataContext() : base()
    {
        //Database.EnsureDeleted();
        Database.EnsureCreated();
    }

    public DbSet<Sensor> Sensors { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlite($"Filename={Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "database.sqlite")}");
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Sensor>(s =>
        {
            s.HasKey(en => en.Id);
            //s.HasIndex(en => en.ClientId).IsUnique();
            s.Property(en => en.Name).IsRequired();
        });
        modelBuilder.Entity<Sensor>()
            .HasData(
                new Sensor { Id = Guid.NewGuid().ToString(), ClientId = "11111", Name = "First item", Description = "This is a private item description.", Payload = "Off" },
                new Sensor { Id = Guid.NewGuid().ToString(), ClientId = "11112", Name = "Second item", Description = "This is a shopping item description.", Payload = "Off" },
                new Sensor { Id = Guid.NewGuid().ToString(), ClientId = "11113", Name = "Third item", Description = "This is a work item description.", Payload = "Off" }
            );
    }

This is the MainPageViewModel code:

    public class MainPageViewModel : ViewModelBase
{
    private readonly INavigationService _navigationService;
    private readonly DataContext _dataContext;
    private static MainPageViewModel _instance;
    private DelegateCommand _addSensorCommand;
    private ObservableCollection<SensorItemViewModel> _sensors;

    public MainPageViewModel(
        INavigationService navigationService,
        DataContext dataContext) : base(navigationService)
    {
        _navigationService = navigationService;
        _dataContext = dataContext;
        _instance = this;
        LoadSensors();
    }

    public DelegateCommand AddSensorCommand => _addSensorCommand ?? (_addSensorCommand = new DelegateCommand(AddSensor));

    public ObservableCollection<SensorItemViewModel> Sensors
    {
        get => _sensors;
        set => SetProperty(ref _sensors, value);
    }

    public static MainPageViewModel GetInstance()
    {
        return _instance;
    }

    private async void AddSensor()
    {
        await _navigationService.NavigateAsync("SensorPage", null);
    }

    public async void LoadSensors()
    {
        try
        {
            var _sensors = await _dataContext.Sensors.ToListAsync();
            Sensors = new ObservableCollection<SensorItemViewModel>(_sensors.Select(s => new SensorItemViewModel(_navigationService)
            {
                Id = s.Id,
                ClientId = s.ClientId,
                Name = s.Name,
                Description = s.Description,
                Payload = s.Payload
            }).ToList());
        }
        catch (Exception ex)
        {
            await App.Current.MainPage.DisplayAlert("Error", ex.Message, "Aceptar");
        }
    }
}

This is the SensorPageViewModel code:

    public class SensorPageViewModel : ViewModelBase
{
    private readonly INavigationService _navigationService;
    private readonly DataContext _dataContext;
    private DelegateCommand _addCommand;
    private Sensor _sensor;
    private string _buttonText;

    public SensorPageViewModel(
        INavigationService navigationService,
        DataContext dataContext) : base(navigationService)
    {
        _navigationService = navigationService;
        _dataContext = dataContext;
    }

    public DelegateCommand AddCommand => _addCommand ?? (_addCommand = new DelegateCommand(Add));

    public string ButtonText
    {
        get => _buttonText;
        set => SetProperty(ref _buttonText, value);
    }

    public Sensor Sensor
    {
        get => _sensor;
        set => SetProperty(ref _sensor, value);
    }

    public override void OnNavigatedTo(INavigationParameters parameters)
    {
        base.OnNavigatedTo(parameters);

        if (parameters.Count >= 1)
        {
            var parameterSensor = parameters.GetValue<SensorItemViewModel>("Sensor");
            Sensor = new Sensor()
            {
                Id = parameterSensor.Id,
                ClientId = parameterSensor.ClientId,
                Name = parameterSensor.Name,
                Description = parameterSensor.Description,
                Payload = parameterSensor.Payload
            };
            Title = Sensor.Name;
            ButtonText = "Editar";
        }
        else
        {
            Sensor = new Sensor();
            Title = "Nuevo sensor";
            ButtonText = "Salvar";
        }
    }

    private async void Add()
    {
        if (ButtonText == "Editar")
        {
            ButtonText = "Salvar";
            Title = "Editar";

            return;
        }

        try
        {
            if (Title == "Editar")
            {
                _dataContext.Sensors.Update(Sensor);
                await _dataContext.SaveChangesAsync();
            }
            else
            {
                Sensor.Id = Guid.NewGuid().ToString();
                _dataContext.Sensors.Add(Sensor);
                await _dataContext.SaveChangesAsync();
            }
        }
        catch (Exception ex)
        {
            await App.Current.MainPage.DisplayAlert("Error", ex.Message, "Aceptar");

            return;
        }

        MainPageViewModel.GetInstance().LoadSensors();
        await _navigationService.GoBackAsync();          
    }
}

This is the SensorItemViewModel code:

public class SensorItemViewModel : Sensor
{
    private readonly INavigationService _navigationService;
    private DelegateCommand _selectSensorCommand;

    public SensorItemViewModel(INavigationService navigationService)
    {
        _navigationService = navigationService;
    }

    public DelegateCommand SelectSensorCommand => _selectSensorCommand ?? (_selectSensorCommand = new DelegateCommand(SelectSensor));

    private async void SelectSensor()
    {
        var parameters = new NavigationParameters
        {
            {"Sensor", this}
        };

        await _navigationService.NavigateAsync("SensorPage", parameters);
    }
}
3
  • Can you show the code of SensorItemViewModel.cs also? Commented Apr 27, 2020 at 3:13
  • Added the SensorItemViewModel.cs and changed the List<SensorItemViewModel> to an ObservableCollection<SensorItemViewModel>. To refresh the ListView after update a value, but doesn't work. Commented Apr 27, 2020 at 11:25
  • 1
    You need to raise 'INotifyCollectionChanged' or you could use 'ObservableCollection' because it implements 'INotifyCollectionChanged'. Commented Apr 27, 2020 at 11:28

2 Answers 2

1

I debuged the progrma flow and I found that when I update item and save the _dataContextin the SensorViewModel it doens't apear in the MainPageViewModel.

Saving the update item in SensorViewModel I added Actualizado in the name:

introducir la descripción de la imagen aquí

But when I read de _dataContext in the MainViewModel it doesn't apear:

introducir la descripción de la imagen aquí

When I added a new item it work fine with out problems.

Sign up to request clarification or add additional context in comments.

Comments

1

The issue here is not that Xamarin.Forms doesn't update the database. When you close and then open the app, you say that you can see the updated value. This means that the value has persisted successfully in the database. The problem is that the UI doesn't know that something has been updated. When you add/remove item from the ObservableCollection, then the collection takes care of that, since it raises OnPropertyChanged for you. However, when you simply update an item (change some of its properties) you need to tell the UI that something has changed. This is done via the INotifyPropertyChanged interface.

See here about the INotifyPropertyChanged

The simplest implementation is this:

public class PropertyChangedViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

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

    protected void SetValue<T>(ref T backingField, T value, [CallerMemberName] string propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(backingField, value))
        {
            return;
        }

        backingField = value;

        OnPropertyChanged(propertyName);
    }
}

After that, you can have your SensorItemViewModel.cs inherit from the PropertyChangedViewModel and raise the PropertyChanged event when you need to.

Another thing - you should never subclass the database entity class with your ViewModel class. The idea of MVVM is totally different than to have the same class twice, sort of. Your SensorItemViewModel should have its own properties and for each property that you want to update dynamically in the UI, you need to raise the PropertyChanged event.

Let's say that you have only one property (Name) and you want to change it dynamically. Then your SensorItemViewModel will look like this:

public class SensorItemViewModel : PropertyChangedViewModel
{
    private string name;

    public string Name
    {
        get => name;
        set => SetValue(ref name, value);
    }
}

Here, each time the Name property changes, it will invoke SetValue method, which will handle the event raising for you, which will notify the UI that the name needs to be changed/redrawn.

2 Comments

Thanks for answer. I'm usin a Xamarin.Forms with a Prism Mvvm and after check the code I found that OnPropertyChanged is included in the ViewModelBase. I'm going to make a basic new project to try to find the mistake.
It doesn't matter if you have it built-in. This is even better then. The solution here is to notify the UI that there is a change somewhere and then the UI will know what to re-render. Use the PropertyChanged in our new project and you will see an immediate update in the UI.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.