0
votes

I'm very new to MVVMCross & Xamarin, so it's very possible I'm missing something simple, but I have an Mvx.MvxGridView layout bound to a simple list of objects.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:local="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <Mvx.MvxGridView
        android:numColumns="5"
        android:verticalSpacing="15dp"
        android:horizontalSpacing="15dp"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        local:MvxBind="ItemsSource Bikes"
        local:MvxItemTemplate="@layout/bikeassignmentview_bikeelement" />
</LinearLayout>

The view is pretty simple:

namespace Keiser.MPM.Screen.Droid.Views
{
    using Android.App;

    [Activity(Theme = "@style/Theme.FullScreen")]
    public class BikeAssignmentView : BaseView
    {
        protected override void OnCreate(Android.OS.Bundle bundle)
        {
            base.OnCreate(bundle);
            this.SetContentView(Resource.Layout.BikeAssignmentView_Page);
        }
    }
}

Same with the view model:

namespace Keiser.MPM.Screen.Core.ViewModels
{
    using System.Collections.Generic;
    using System.Windows.Input;
    using Cirrious.CrossCore;
    using Cirrious.MvvmCross.ViewModels;
    using Keiser.MPM.Screen.Core.Models.Bikes;

    public class BikeAssignmentViewModel : BaseViewModel
    {
        private IBikeManagerService _bikeManagerService;
        private List<Bike> _bikes;
        public List<Bike> Bikes { get { return _bikes; } }

        public BikeAssignmentViewModel(IBikeManagerService bikeManagerService)
        {
            _bikeManagerService = bikeManagerService;
            _bikes = _bikeManagerService.Bikes;
        }
    }
}

The service where the Bikes list is actually originating is nested all the way down in a service class:

namespace Keiser.MPM.Screen.Core.Models.Bikes
{
    using System;
    using System.Linq;
    using System.Threading;
    using System.Collections.Generic;
    using Cirrious.CrossCore;
    using Cirrious.CrossCore.Core;
    using Cirrious.MvvmCross.ViewModels;
    using Cirrious.MvvmCross.Plugins.Messenger;
    using Core.Models.Settings;

    public class BikeManagerService : MvxNotifyPropertyChanged, IBikeManagerService
    {
        public object BikesLocker = new object();
        private List<Bike> _bikes = new List<Bike>();
        public List<Bike> Bikes
        {
            get { return _bikes; }
            set { _bikes = value;  RaisePropertyChanged(() => Bikes); }
        }

        // --- Other boring code...
    }
}

Here's the issue. The grid view won't populate dynamically at all if the list is empty when the view is loaded. If I enter the page with the list populated, it will load correctly, and the grids will be added for new objects added to the list, but it will not remove disposed grids from the list until I click on the screen a bit. The objects continue to update correctly until the object is disposed. Then the fields of the object stop working, but they don't disappear. Also, if the list ever goes back to empty, the view won't ever update again.

Am I missing something? Should I be invalidating the view or something? Any help or resources would be greatly appreciated!

[======================= Solution =======================]

The final solution was to convert the list to an observable collection: Interface:

namespace Keiser.MPM.Screen.Core.Models.Helpers
{
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.ComponentModel;

    public interface IObservableCollection<T>
        : IList<T>
          , INotifyPropertyChanged
          , INotifyCollectionChanged
    {
    }
}

Class:

namespace Keiser.MPM.Screen.Core.Models.Helpers
{
    using System.Collections.Generic;
    using System.Collections.ObjectModel;

    public class SimpleObservableCollection<T>
        : ObservableCollection<T>
        , IObservableCollection<T>
    {
        public SimpleObservableCollection(List<T> source) : base(source) { }

        protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            base.OnCollectionChanged(e);
        }
    }
}

All changes made to the Collection had to be done on the main UI thread, which began to degrade performance (I'm guessing from the continual context switching?). I ended up scrapping the Observable list and implementing an IEnumerable class which fires a message on changes and subscribing to the message in the view:

namespace Keiser.MPM.Screen.Droid.Views
{
    using Android.App;
    using Android.Widget;
    using Cirrious.MvvmCross.Plugins.Messenger;

    [Activity(Theme = "@style/Theme.FullScreen")]
    public class BikeAssignmentView : BaseView
    {
        protected MvxSubscriptionToken _BikeListToken;

        protected override void OnCreate(Android.OS.Bundle bundle)
        {
            base.OnCreate(bundle);
            this.SetContentView(Resource.Layout.BikeAssignmentView_Page);
            var gridView = FindViewById<GridView>(Resource.Id.gridview);

            _BikeListToken = Cirrious.CrossCore.Mvx.Resolve<IMvxMessenger>().SubscribeOnMainThread<Core.Models.Bikes.BikesChangedMessage>(message =>
            {
                ((BaseAdapter)gridView.Adapter).NotifyDataSetChanged();
            });
        }
    }
}
1

1 Answers

1
votes

Normal Mvvm and Data-Binding works using INotifyPropertyChanged

This means that when your Grid in the UI binds its ItemsSource to Bikes on the BikeAssignmentViewModel then it hooks into the PropertyChanged event on BikeAssignmentViewModel

Since you are firing RaisePropertyChanged from your Service and not from your ViewModel then the Grid never sees this change notification.

To work around this:

  • you could find a way to raise the RaisePropertyChange call from the ViewModel rather than the Service
  • you could find a way to bind the Grid to the Service rather as well as to the ViewModel (e.g. bind ItemsSource Service.Books)

If you're new to Data-Binding then it may also be worth reading up more about Data-Binding and for lists also learning about ObservableCollection and INotifyCollectionChanged