Question

My table view does not update whenever its source property is changed. The code is as follows:

ViewController:

    public override void ViewDidLoad()
    {
        base.ViewDidLoad();
        View.BackgroundColor = UIColor.White;

        var table = new UITableView(new RectangleF(0, 80, Device.Width, Device.Height - 80));
        Add(table);
        var source = new MvxStandardTableViewSource(table, "TitleText SessionInfo");
        table.Source = source;

        var set = this.CreateBindingSet<JoinSessionViewController, JoinSessionViewModel>();
        set.Bind(source).To(vm => vm.AvailableServers);
        set.Apply();

        table.ReloadData();
    }

ViewModel:

    private readonly INetworkSessionClient _client;

    public JoinSessionViewModel(INetworkSessionClient client)
    {
        _client = client;

        _client.ServerFound += ClientOnServerFound;
        _client.BeginSearchingForServers();
    }

    private void ClientOnServerFound(object sender, ServerFoundEventArgs serverFoundEventArgs)
    {
        if (AvailableServers.Any(s => s.Identifier == serverFoundEventArgs.ServerInfo.Identifier))
            return;

        AvailableServers.Add(serverFoundEventArgs.ServerInfo);
        RaisePropertyChanged(() => AvailableServers);
    }

    private List<ServerInfo> _availableServers;
    public List<ServerInfo> AvailableServers
    {
        get { return _availableServers; }
        set { _availableServers = value; RaisePropertyChanged(() => AvailableServers); }
    }
Was it helpful?

Solution

This was a tricky one. The culprit is this line in MvxTableViewSource:

https://github.com/MvvmCross/MvvmCross/blob/v3.1/Cirrious/Cirrious.MvvmCross.Binding.Touch/Views/MvxTableViewSource.cs#L56

    public virtual IEnumerable ItemsSource
    {
        get { return _itemsSource; }
        set
        {
            if (_itemsSource == value) // **** This one ****
                return;

            if (_subscription != null)
            {
                _subscription.Dispose();
                _subscription = null;
            }

            _itemsSource = value;

            var collectionChanged = _itemsSource as INotifyCollectionChanged;
            if (collectionChanged != null)
            {
                _subscription = collectionChanged.WeakSubscribe(CollectionChangedOnCollectionChanged);
            }

            ReloadTableData();
        }
    }

Since I was only adding to the list and not setting it to a new list, the setter was early returning and not firing ReloadTableData(). The fix was to use an ObservableCollection (or anything else that implements INotifyCollectionChanged) instead of a List.

    private void ClientOnServerFound(object sender, ServerFoundEventArgs serverFoundEventArgs)
    {
        if (AvailableServers.Any(s => s.Identifier == serverFoundEventArgs.ServerInfo.Identifier))
            return;

        // CollectionChanged event is not automatically martialed to UI thread
        InvokeOnMainThread(() => AvailableServers.Add(serverFoundEventArgs.ServerInfo));
    }

    private ObservableCollection<ServerInfo> _availableServers;
    public ObservableCollection<ServerInfo> AvailableServers
    {
        get { return _availableServers; }
        set { _availableServers = value; RaisePropertyChanged(() => AvailableServers); }
    }

Note that because of my particular case, the ServerFound event is being invoked on a separate thread. That means the INotifyCollectionChanged.CollectionChanged event will also be invoked on a separate thread, so the action of adding the server to the list has to be martialed onto the main thread with InvokeOnMainThread() so that the UI will properly update.

It would be nice if CollectionChanged events would automatically be handled on the UI thread, similar to how RaisePropertyChanged() works.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top