Search code examples
c#xamarinmvvmdata-bindingbarcode-scanner

OnPropertyChanged not updating UI for xzing barcode scanner for xamarin


I'm using mvvm thoughout my app and data binding and property changes are working everywhere else in the application but for some reason, on my view using XZing Barcode Scanner, my view is not updating after I scan an item.

I'm used to working with XAML ViewModel and Codebehind but they way I set up the barcode scanner is only ViewModel and Codebehind/alone class. So maybe I'm missing something simple that I am just not used to doing.

I've posted some code below that is an example of just one of the properties not updating, the PartName for productsLable.Text. It is bound when the page first loads and the dummy PartName shows correctly but after scanning a new item, I update the ScannedPart in my ViewModel but the "get" is never called after I set and call OnPropertyChanged();

This is working well in other parts of my application but maybe I am missing something silly here, like I said I am more comfortable working with bindings in XAML. Do you see anything I might be doing wrong or missing?

Thanks in advance.

public class TimsCustomScanPage : ContentPage
    {
        ZXingScannerView zxing;
        TimsCustomOverlay overlay;
        RequestPartsViewModel viewModel;

        public TimsCustomScanPage(RequestPartsViewModel viewModel) : base()
        {
           //Attaching ViewModel
            this.viewModel = viewModel;
            this.BindingContext = viewModel;
            //Scanner found barcode
            zxing.OnScanResult += (result) =>
            {
                // Stop analysis until we navigate away so we don't keep reading barcodes
                zxing.IsAnalyzing = false;
                //Setting new scanned part
                viewModel.ScannedPart = viewModel.Parts.FirstOrDefault();
            };
--Some more code--
 
var productsLabel = new Label()
            {
                //Text = "Find New Part",
                TextColor = Color.Black,
                WidthRequest = 150,
                HorizontalOptions = LayoutOptions.Start,
                VerticalTextAlignment = TextAlignment.Center,
                Padding = new Thickness(0, 0, 0, 0),
            };
            //Binding text property 
            productsLabel.SetBinding(Label.TextProperty, "PartName");
            productsLabel.BindingContext = viewModel.ScannedPart;
}

ViewModel

public class RequestPartsViewModel : BaseViewModel
    {
        public ObservableCollection<TimsCategory> Categories { get; set; }
        public ObservableCollection<TimsCategory> FavoriteCategories { get; set; }
        public ObservableCollection<TimsPart> Parts { get; set; }
        public TimsCategory CurrentSelection { get; set; }
        public Command LoadItemsCommand { get; set; }
        TimsPart scannedPart;
        string partName;
        int totalRequestedParts;

        public RequestPartsViewModel()
        {
            Title = "Request Parts";
            Categories = new ObservableCollection<TimsCategory>(db.Table<TimsCategory>().ToList());
            Parts = new ObservableCollection<TimsPart>(db.Table<TimsPart>().ToList());
            TotalRequestedParts = 0;
            ScannedPart = new TimsPart();
            ScannedPart.PartName = "Scan New Part";
            ScannedPart.AvailableQuantity = 0;

            //Attach parts to categories 
            foreach (var item in Categories)
            {
                int newId = item.CategoryId;
                var partsForCategories = Parts.Where(p => p.CategoryId == newId);
                var partsForCategoriesCollection = new ObservableCollection<TimsPart>(partsForCategories);
                item.Parts = partsForCategoriesCollection;
            }
            
            FavoriteCategories = new ObservableCollection<TimsCategory>(Categories.Take(6).ToList());

            LoadItemsCommand = new Command(async () => await ExecuteLoadItemsCommand());
        }

        public TimsPart ScannedPart
        {
            get
            {
                return scannedPart;
            }
            set
            {
                if (scannedPart != value)
                {
                    scannedPart = value;
                    OnPropertyChanged("PartName");
                    OnPropertyChanged("RequestedQuantity");
                    OnPropertyChanged("AvailableQuantity");
                    OnPropertyChanged("ScannedPart");
                }
            }
        }

Base ViewModel

public class BaseViewModel : INotifyPropertyChanged
    {
        public SQLiteConnection db = TimsData.database;

        bool isBusy = false;
        public bool IsBusy
        {
            get { return isBusy; }
            set { SetProperty(ref isBusy, value); }
        }

        string title = string.Empty;
        public string Title
        {
            get { return title; }
            set { SetProperty(ref title, value); }
        }

        protected bool SetProperty<T>(ref T backingStore, T value,
            [CallerMemberName]string propertyName = "",
            Action onChanged = null)
        {
            if (EqualityComparer<T>.Default.Equals(backingStore, value))
                return false;

            backingStore = value;
            onChanged?.Invoke();
            OnPropertyChanged(propertyName);
            return true;
        }

        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
        {
            var changed = PropertyChanged;
            if (changed == null)
                return;

            changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
        #endregion
    }

Solution

  • I think the problem is that you are binding directly to a property, but then changing the base object, not just the property. Try this instead

    productsLabel.SetBinding(Label.TextProperty, "ScannedPart.PartName");
    // you already have a BindingContext set for your page that the Label will inherit
    // productsLabel.BindingContext = viewModel.ScannedPart;