Friday, December 21, 2012

DataBinding dynamic Collections/Lists in Monodroid (and beyond)

From http://stackoverflow.com/questions/13987906/cant-bind-a-mvxbindablelistview-in-twoway-mode

The way data-binding works is through an interface called INotifyPropertyChanged

What happens in this interface is that the ViewModel sends the View a message whenever a property changes - e.g.

    FirePropertyChanged("TestList");

With a list, this doesn't help if the contents of the list itself change - e.g. when the list has an item added or removed.



To solve this, the .Net Mvvm implementation includes another interface INotifyCollectionChanged.

A collection - such as a list - can implement INotifyCollectionChanged in order to let the View know when the contents of the collection change.

For example, the collection might fire events containing hints such as:
  • everything has changed - NotifyCollectionChangedAction.Reset
  • an item has been added - NotifyCollectionChangedAction.Add
  • an item has been removed - NotifyCollectionChangedAction.Remove
  • ...
There's a short introduction into this interface about 12:30 into the MvvmCross Xaminar http://www.youtube.com/watch?v=jdiu_dH3z5k

Xaminar



To use this interface for a small in-memory list - e.g. less than 1000 'small' objects - all you have to do is to change your List for an ObservableCollection - the ObservableCollection is a class from the core .Net libraries (from Microsoft or Mono) and it will fire the correct events when you Add/Remove list items.

You can see the source for the Mono ObservableCollection implementation in: https://github.com/mosa/Mono-Class-Libraries/blob/master/mcs/class/System/System.Collections.ObjectModel/ObservableCollection.cs - it is worth taking some time to look at this implementation so that you can understand a bit more about how Mvvm works with INotifyCollectionChanged.

If you use the ObservableCollection class, then your code will become:

    private ObservableCollection<MyType> _testList;
    public ObservableCollection<MyType> TestList
    {
        get { return _testList; }
        set
        {
            _testList = value;
            FirePropertyChanged("TestList");
            // in vNext use RaisePropertyChanged(() => TestList);
        }
    }

with:

 
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    local:MvxBind="{'ItemsSource':{'Path':'TestList'}}"
    local:MvxItemTemplate="@layout/my_item_layout" />

Note:
  • that the binding is OneWay - this means that binding is still only going from ViewModel to View - there are no updates going from View to ViewModel.
  • that ObservableCollection is designed to be single-threaded - so make sure all changes to the collection are done on the UI thread - not on a worker thread. If you need to, you can marshall work back onto the UI thread using InvokeOnMainThread(() => { /* do work here */ }) in a ViewModel.
  • that in Android, the way lists work (through the base AdapterView) means that every time you call any update on the ObservableCollection then the UI List will ignore the action hint (Add, Remove, etc) - it will treat every change as a Reset and this will cause the entire list to redraw.


For larger collections - where you don't want all the items in memory at the same time - you may need to implement some data-store backed list yourself.

There is a brief example of one simple sqlite data-backed store in https://github.com/slodge/MvvmCross/blob/vnext/Sample%20-%20SimpleDialogBinding/SimpleDroidSql.Core/DatabaseBackedObservableCollection.cs

This virtualizing of collection data is common in WP and WPF apps - e.g. see questions and answers like Is listbox virtualized by default in WP7 Mango?

No comments:

Post a Comment