Model changes in one page not displaying on another

NotEnoughTimeNotEnoughTime Member ✭✭
edited January 20 in Xamarin.Forms

I am struggling to have model changes on one page display on another page. I am using a singleton class which has an collection of items. My intention is to have a single collection of items shared by two pages and have changes to those items be broadcast to both pages. I am using MVVM and

CategoryCache
Singelton Class - Exposes Collection of Category class

Category Class
POCO class, implements NotifyPropertyChanged on property setters

Page1
Takes CategoryCache in constructor - Gets Category collection from CategoryCache
Displays Category Collection in a picker
Has a Manage Categories button to show ManageCategoriesPage

ManageCategoriesPage
Takes CategoryCache in constructor - Gets Category collection from CategoryCache
Displays Category Collection in a ListView

Use Case 1. Add a new Category
Use Case 2. Edit a Category Name

UC1 This works as expected
* Add a new item to the CategoryCache collection
* ManageCategories.ListView shows the new item
* Navigate back to Page1
* The new Category appears in the Category picker

UC2 This does not work as expected
* Edit a category name
* ManageCategoriesPage.ListView shows the updated name
* Navigate back to Page1
* The Category picker DOES NOT show the updated name

I have tried using ObservableCollection and BindingList for the Category collection. Both types have identical behavior for both use cases.

I have researched both types and have found many posts which indicate that this should work. I have considered using MessagingCenter to notify the Page1 that Category collection changed and trigger a manual update in the ViewModel but that seems heavy handed.

I just realized I didn't add a question...

Is there a reason why changing properties at the model level would trigger an update to the ManageCategories.ListView but not the Page1.Picker? At first I thought maybe it was because Page1 was not in view anymore, but adding an item to the CategoryCache.CategoryCollection does trigger Page1.Picker to update.

What makes me even more confused is if I update the name of Category1, then add a new category, Page1.Picker shows the new category, but still does not show the updated name for Category1.

I am using:
VS 2017 Pro v15.9.5
Xamarin.Forms 3.4.0.1009999
Xamarin.Forms.Platform.WPF 3.4.0.100999
Autofac 4.8.1

This is my first time posting, so if I have omitted important information please let me know.

Answers

  • JohnHardmanJohnHardman GBUniversity mod
    edited January 20

    @NotEnoughTime said:
    I have considered using MessagingCenter to notify the Page1 that Category collection changed and trigger a manual update in the ViewModel

    Don't do that.

    @NotEnoughTime said:
    I have tried using ObservableCollection

    ObservableCollection will do what you want.

    I just knocked together a small sample, that works in both of your scenarios. I've only tested it on UWP and Android, but I would expect it to work on other platforms (note that there is one oddity, in that when a member of the collection is changed, the Picker refreshes in its unexpanded form, not showing the currently selected item when using XF 3.4.0.1008975):

    ViewModel

        public class PhoneticAlphabetVM
        {
            private ObservableCollection<string> _letters = new ObservableCollection<string>
            {
                "alpha",
                "bravo",
                "charlie",
                "delta",
                "echo",
                "foxtrot",
                "golf",
                "hotel",
                "india",
                "juliet",
                "kilo",
                "lima",
                "mike",
                "november",
                "oscar",
                "papa",
                "quebec",
                "romeo",
                "sierra",
                "tango",
                "uniform"
            };
    
            public void AddItem(string newItem)
            {
                _letters.Add(newItem);
            }
    
            public void ModifyItem()
            {
                _letters[0] = _letters[0] + "*";
            }
    
            public IEnumerable ItemsSource => _letters;
        }
    

    Picker

    PhoneticAlphabetVM phoneticAlphabetVM = new PhoneticAlphabetVM();
    Picker picker = new Picker
    {
        Title = "Picker title",
        BindingContext = phoneticAlphabetVM
    };
    
    picker.SetBinding(
        Picker.ItemsSourceProperty,
        nameof(PhoneticAlphabetVM.ItemsSource));
    

    The picker and a couple of Buttons to test the scenarios, that get included in a page UI

    picker,
    new Button { Text = "Add item to picker", Command = new Command(() =>
    {
        phoneticAlphabetVM.AddItem("Zulu");
    })},
    new Button { Text = "Modify first picker item", Command = new Command(() =>
    {
        phoneticAlphabetVM.ModifyItem();
    })},
    

    A workaround for the selected item not showing after a modification, is simply to do:

    int index = picker.SelectedIndex;
    phoneticAlphabetVM.ModifyItem();
    picker.SelectedIndex = index;
    
  • NotEnoughTimeNotEnoughTime Member ✭✭

    I agree that it should work. However it doesn't in my solution. Your example is significantly simpler than what I have.

    In my example I have two View/ViewModels that both have a reference to a SingleInstance class which has the ObservableCollection that both ViewModels reference.

    When I add an item to the collection in the SingleInstance class, the View that is currently displayed is updated. When I navigate back to the other page, it's View has also been updated with the new item.

    When I edit an existing item in the collection in the SingleInstance class, only the View that is currently displayed updates. When I navigate back to the other page, the View has not been updated the items text change.

  • JohnHardmanJohnHardman GBUniversity mod

    @NotEnoughTime said:
    In my example I have two View/ViewModels that both have a reference to a SingleInstance class which has the ObservableCollection that both ViewModels reference.

    I wonder if somehow either INotifyCollectionChanged or INotifyPropertyChanged is not getting passed through to the View. That could be a result of an issue in the View Model, or could be a problem with the View doing wiring & unwiring of events in OnAppearing and OnDisappearing rather than only unwiring when the page is popped. You'll probably need to post your code.

  • NotEnoughTimeNotEnoughTime Member ✭✭

    I tried adding a ListView to Page1 bound to the same collection. So now Page1 has a Picker and a ListView bound to the same Categories collection. That new ListView control updates when I edit an item in the ManageCategories view.

    However, when I add a new item in the ManageCategories view, the ListView on that page does not update anymore. The Picker on Page1 does not update anymore, but the new ListView control on Page1 does update with the new item.

    It's almost as if the new ListView control on Page1 is eating the CollectionChanged event, preventing the other two controls from updating.

    I am finding this problem to be quite infuriating. I am a Xamarin novice. I have quite a bit of experience with WPF. I've never had problems with databinding before, but I've never tried binding multiple pages/views to the same collection before.

    The main idea is that when you create a list there are types you can choose from. If you wanted to add/edit a type you would click Manage Types, add or edit a type, then navigate back to complete the list creation.

    Is this a bad design? (I know that is an opinion based question, but at this point I'm so frustrated this isn't working I'm beginning to be believe I'm taking the wrong approach)

    I tried posting screenshots to illustrate the idea, but the website has decreed: You have to be around for a little while longer before you can post links.

  • NotEnoughTimeNotEnoughTime Member ✭✭

    I am now convinced this is a bug with the Picker.

    Just to get things moving I added code to manually set the list anytime an item is updated in the CategoryCache using MessagingCenter.

    I could not get the Picker items to update by simply setting RetailListViewModel.RetailListTypes = the updated collection. However if I set RetailListViewModel.RetailListTypes = new ObservableCollection(updated collection) then it works.

    I tried adding code into this post, but most of it disappeared. Here are some snippets:

        public RetailListViewModel(INavigationService navigationService
            , LookupItemCache lookupItemCache
            , RetailListRepository retailListRepository
            , IRepository<Retailer> retailerRepository
            , ILookupRepository<RetailListType> retailListTypeRepository)
            : base(navigationService)
        {
            _lookupItemCache = lookupItemCache;
            _retailListRepository = retailListRepository;
            _retailerRepository = retailerRepository;
            _retailListTypeRepository = retailListTypeRepository;
            MessagingCenter.Subscribe<ILookupRepository<RetailListType>>(_retailListTypeRepository, "UpdateRetailListType", (sender) =>
            {
                RetailListTypes = _retailListTypeRepository.AllEntities;
                //RetailListTypes = new ObservableCollection<RetailListType>(_retailListTypeRepository.AllEntities);
            });
        }
    
        private ObservableCollection<RetailListType> _RetailListTypes;
        public ObservableCollection<RetailListType> RetailListTypes
        {
            get { return _RetailListTypes; }
            set
            {
                //in my attempts to get this working I removed the "if (_RetailListTypes != value)" check
                _RetailListTypes = value;
                NotifyPropertyChanged();
            }
        }
    

    The code above does not refresh the Picker. I put a breakpoint on the line: RetailListTypes = _retailListTypeRepository.AllEntities;

    I can see that the collection has been updated, but the View doesn't reflect the items in the list I just set.

    If I use this line instead, then the Picker does update.
    RetailListTypes = new ObservableCollection(_retailListTypeRepository.AllEntities);

Sign In or Register to comment.