Vitalii Vasylenko Vitalii Vasylenko - 3 months ago 18
C# Question

Modern approach for UITableView grouping with Mvvmcross

I had found several (not too many) different approaches for grouping cells in Xamarin+mvvmcross. I've tried couple of them, but i'm getting a problem: when my real service returns updated collection (with a small delay), binding is not really working and list remains empty. If to run fake service, which returns result instantly, list is filled with data.

I've tried several approaches, but no one pushed me forward, so i'm not sure if any code is necessary. Just asking if there's a sample/hints for the modern implementation of the grouping.

EDIT: here's a code sample. Current version is based on Stuart's answer: Unable to bind to ios table section cells in mvvmcross, child cells bind as expected

ViewModel:

public override async void OnShow()
{
var calendarList = await DataService.GetListAsync();

CalendarList = new List<Model>(calendarList.OrderBy(a => a.Date));
}


So, viewmodel gets a list of Models, order it by Date and set it to CalendarList. CalendarList is just a List which throws notifications (new is doing this job).

View, initializing:

public override void ViewDidLoad()
{
base.ViewDidLoad();

var source = new TableSource(CalendarList);

this.AddBindings(new Dictionary<object, string>
{
{source, "ItemsSource CalendarList" }
});

CalendarList.Source = source;
CalendarList.ReloadData();
}


View, TableSource

public class TableSource : MvxSimpleTableViewSource
{
private static readonly NSString CalendarCellIdentifier = new NSString("CalendarCell");
private List<IGrouping<DateTime, Model>> GroupedCalendarList;

public TableSource(UITableView calendarView) : base(calendarView, "CalendarCell", "CalendarCell")
{
}


public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
var foo = GroupedCalendarList[indexPath.Section].ToList();
var item = foo[indexPath.Row];

var cell = GetOrCreateCellFor(tableView, indexPath, item);

var bindable = cell as IMvxDataConsumer;
if (bindable != null)
bindable.DataContext = item;

return cell;
}

public override nint RowsInSection(UITableView tableview, nint section)
{
var numberRows = GroupedCalendarList[(int)section].Count();
return numberRows;
}

public override UIView GetViewForHeader(UITableView tableView, nint section)
{
var label = new UILabel();
label.Text = GroupedCalendarList[(int)section].FirstOrDefault().ScheduledStart.Value.Date.ToString();
label.TextAlignment = UITextAlignment.Center;

return label;
}

public override nint NumberOfSections(UITableView tableView)
{
return GroupedCalendarList.Count;
}

public override void ReloadTableData()
{
if (ItemsSource == null) return;
var calendarList = new List<Model>(ItemsSource as List<ActivityModel>);

List<IGrouping<DateTime, Model>> groupedCalendarList = calendarList.GroupBy(cl => cl.ScheduledStart.Value.Date).ToList();

if (ItemsSource != null)
GroupedCalendarList = new List<IGrouping<DateTime, Model>>(groupedCalendarList);
}
}


One of the symptoms is that ReloadTableData() is not called when VM is updating the list.

EDIT 2: well, i've tried to use ObservableCollection and i saw that ReloadTableData is called, but UI is still empty. If anyone can provide a working sample - that'd be great.

Updated VM:

public override async void OnShow()
{
var calendarList = await DataService.GetListAsync();

CalendarList.Clear();
foreach (var item in calendarList.OrderBy(a => a.ScheduledStart.Value.Date))
{
CalendarList.Add(item); // Lets bruteforce and send notification on each add. In real life it's better to use extension ObservableCollection.AddRange().
}
}


Updated View:

public override void ReloadTableData()
{
if (ItemsSource == null) return;
var calendarList = ItemsSource as IEnumerable<Model>;

var groupedCalendarList = calendarList.GroupBy(cl => cl.ScheduledStart.Value.Date).ToList();

GroupedCalendarList = new List<IGrouping<DateTime, Model>>(groupedCalendarList);

Mvx.Trace("Trying to add new item " + calendarList.Count());
}


In that case, output is full of


Diagnostic: 9.54 Trying to add new item 77


But the UI list is still empty.

EDIT3: If to replace VM.OnShow() with adding Task.Run().Wait(); to the VM's ctor (so it would delay View.ViewDidLoad() until data is loaded) - then list shows correctly.

public ViewModel()
{
Task.Run(async () =>
{
var calendarList = await DataService.GetListAsync();
CalendarList = new ObservableCollection<ActivityModel>(calendarList);
}).Wait(); // This guy is called before View.ViewDidLoad() and grouped list is shown properly
}

Answer

I'm blind! Thanks to nm_dev from MvvmCross Slack channel for hignlightning the problem. I forgot to call base.ReloadTableData().

And ofc thanks for Pilatus and Springham for good hints.

Here's the solution:

    public override void ReloadTableData()
    {
        if (ItemsSource == null) return;
        var calendarList = new List<Model>(ItemsSource as List<ActivityModel>);

        List<IGrouping<DateTime, Model>> groupedCalendarList = calendarList.GroupBy(cl => cl.ScheduledStart.Value.Date).ToList();

        if (ItemsSource != null)
            GroupedCalendarList = new List<IGrouping<DateTime, Model>>(groupedCalendarList);

        base.ReloadTableData(); // Here we are
    }