Xamarin.Forms ListView Advanced Guide With MVVM

Xamarin.Forms Listview MVVM

Hello Friends, If you have been building Xamarin.Forms applications, you surely have noticed that the ListView is one of the controls you will use most often. The ListView has several functionalities and serves primarily in displaying a list of data. Taking this into consideration, you will notice that data displayed on list views need to provide interaction per data cell, different presentations of the data at run time, and several others. All these could easily be achieved i Xamarin.Forms but for a developer who is always in need of building highly maintainable applications, with clean code and good design patterns, he should be able to achieve this using MVVM architectural design pattern.

In this blog post, we will go through implementing these advanced functionalities with the list view, in an MVVM application. Without breaking our application’s architectural design pattern. At first glance, it will be easier to implement some of these functionalities using code behind Xaml, and going on with building the application, but in the long run this can be risky for app maintenance and addition of new functionalities. Therefore, implementing these functionalities while respecting MVVM design pattern will be very beneficial for your Xamarin.Forms application.

Note:

We will be using ReactiveUI for MVVM in this application. Here is the source cod for this demo and Here is another demo on ReactiveUI for more details.

What we will be doing

  • Binding commands from each list cell to the list’s view model
  • Responding to List item tapped in viewmodel
  • Context menus in list view
  • Data template selection
  • ListView Grouping

Handling List Item Tapped in the ViewModel’s Command

When you have a list view in you app, you will most often need to respond to item tapped events from the user. If you are in an MVVM application, you should know that events are replaced by commands and that the view model should have nothing to do with the view components only data binding should link both of them. So how should we go about listening to these item tapped events in such a situation. This is done using Event to Command behaviors what it does is convert the event into a command, pass in command parameters and convert the parameter if necessary for the view model to handle this easily. In our case, this will be done in our navigation page, where when a menu item is clicked on, it navigated to the required page immediately. Here are the key points of this implementation.

public class EventToCommandBehavior : BaseBehavior<View>
    {
        Delegate eventHandler;

        public static readonly BindableProperty EventNameProperty = BindableProperty.Create("EventName", typeof(string), typeof(EventToCommandBehavior), null, propertyChanged: OnEventNameChanged);
        public static readonly BindableProperty CommandProperty = BindableProperty.Create("Command", typeof(ICommand), typeof(EventToCommandBehavior), null);
        public static readonly BindableProperty InputConverterProperty = BindableProperty.Create("Converter", typeof(IValueConverter), typeof(EventToCommandBehavior), null);

        public string EventName
        {
            get { return (string)GetValue(EventNameProperty); }
            set { SetValue(EventNameProperty, value); }
        }

        public ICommand Command
        {
            get { return (ICommand)GetValue(CommandProperty); }
            set { SetValue(CommandProperty, value); }
        }


        //EventItemToMenuItemConverter _converter = new EventItemToMenuItemConverter();
        public IValueConverter Converter
        {
            get
            {
                return (IValueConverter)GetValue(InputConverterProperty);
            }
            set { SetValue(InputConverterProperty, value); }
        }

        protected override void OnAttachedTo(View bindable)
        {
            base.OnAttachedTo(bindable);
            RegisterEvent(EventName);
        }

        protected override void OnDetachingFrom(View bindable)
        {
            DeregisterEvent(EventName);
            base.OnDetachingFrom(bindable);
        }

        void RegisterEvent(string name)
        {
            if (string.IsNullOrWhiteSpace(name))
            {
                return;
            }

            EventInfo eventInfo = AssociatedObject.GetType().GetRuntimeEvent(name);
            if (eventInfo == null)
            {
                throw new ArgumentException(string.Format("EventToCommandBehavior: Can't register the '{0}' event.", EventName));
            }
            MethodInfo methodInfo = typeof(EventToCommandBehavior).GetTypeInfo().GetDeclaredMethod("OnEvent");
            eventHandler = methodInfo.CreateDelegate(eventInfo.EventHandlerType, this);
            eventInfo.AddEventHandler(AssociatedObject, eventHandler);
        }

        void DeregisterEvent(string name)
        {
            if (string.IsNullOrWhiteSpace(name))
            {
                return;
            }

            if (eventHandler == null)
            {
                return;
            }
            EventInfo eventInfo = AssociatedObject.GetType().GetRuntimeEvent(name);
            if (eventInfo == null)
            {
                throw new ArgumentException(string.Format("EventToCommandBehavior: Can't de-register the '{0}' event.", EventName));
            }
            eventInfo.RemoveEventHandler(AssociatedObject, eventHandler);
            eventHandler = null;
        }

        void OnEvent(object sender, object eventArgs)
        {
            object resolvedParameter = new object();

            if (Command == null)
                return;
            
            else if (Converter != null)
            {
                resolvedParameter = Converter.Convert(eventArgs, typeof(object), null, null);
            }
            else
            {
                var arg = eventArgs as ItemTappedEventArgs;
                if (arg == null)
                {
                    resolvedParameter = eventArgs;
                }
                else
                {
                    resolvedParameter = arg.Item;
                }

                //resolvedParameter = eventArgs;
            }

            if (Command.CanExecute(resolvedParameter))
            {
                Command.Execute(resolvedParameter);
            }
        }


        static void OnEventNameChanged(BindableObject bindable, object oldValue, object newValue)
        {
            var behavior = (EventToCommandBehavior)bindable;
            if (behavior.AssociatedObject == null)
            {
                return;
            }

            string oldEventName = (string)oldValue;
            string newEventName = (string)newValue;

            behavior.DeregisterEvent(oldEventName);
            behavior.RegisterEvent(newEventName);
        }
    }

Above is the EventToCommandBehavior, its role is to convert the item clicked event to a command which will be handed to the view model it also can serve in performing a set of actions on the data passed from the event before it reaches the viewmodel’s command.

And you add this behavior to the list view as shown bellow.

<ListView.Behaviors>

                <behaviors:EventToCommandBehavior Command="{Binding NavigationItemSelectedCommand}"
                                                  EventName="ItemTapped"/>

            </ListView.Behaviors>

Data Template Selection in a List View

Most often, the set of data which you want to present in a list view is not exactly identical, some items may differ and you want to present it at run time to the user differently. This is done using a template selector. Its role is clear, it examines the items in the list view at run time and returns the appropriate template. In our demo, this is demonstrated with the todo page, with to do items being presented differently depending on whether they were completed or not. I used some code from my previous sample tutorial on reactiveui here  here is the code corresponding to this.

public class TodoTemplateSelector : DataTemplateSelector
   {
       public DataTemplate PrimaryItemTemplate { get; set; }
       public DataTemplate SecondaryItemTemplate { get; set; }

       protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
       {
           var todo = item as Todo;

           return todo.IsDone ? SecondaryItemTemplate : PrimaryItemTemplate;
       }
   }

It is easy to add this to the list view, as shown below.

<!-- Do this inside a resource dictionary !!!!!!!!-->

<dataTemplateSelector:TodoTemplateSelector x:Key="TemplateSelector"
                                                           PrimaryItemTemplate="{StaticResource CurrentTodoDataTemplate}"
                                                           SecondaryItemTemplate="{StaticResource CompletedTodoDataTemplate}"/>
<ListView x:Name="MyListView"
           ItemsSource="{Binding Todos}"
           SelectedItem="{Binding SelectedTodo}"
             ItemTemplate="{StaticResource TemplateSelector}"
           CachingStrategy="RecycleElement">

       </ListView>

Adding Context Menus on List View Items

This task consists of showing a context menu when the list view items are either right clicked or pressed for a longer period of time. These menus will have actions which are called and performed on the item clicked. It is accomplished easily, and this is done in the view cells which will serve as template for the list view as shown below.

<ViewCell.ContextActions>
        <MenuItem Command="{Binding BaseContext.DeleteTodoCommand, Source={x:Reference Template}}" 
                  CommandParameter="{Binding .}"  x:Name="DeleteMenuItem" Text="Delete"/>
    </ViewCell.ContextActions>

There are a few steps in the code which you may not know what they are for, don’t worry we will cover that in the next section.

Binding Commands From Each List Cell to the List’s ViewModel

You have probably wanted code from your list data templates to call commands from your view model directly and pass the current item to the view model to perform necessary actions, this is done easily with Xaml and Without Breaking MVVM.

  1. We need to create properties in the list data templates. These properties will contain their’s parent view’s BindingContext and commands will be gotten from them.
  2. Pass the Binding context from xaml to the data template
  3. Receive the binding context in the data template, bind commands and pass the current item as the command’s parameter.

these steps are clear and here are their implementations.

Step 1

public partial class CompletedTodoTemplate : ViewCell
    {
        public static readonly BindableProperty BaseContextProperty =
              BindableProperty.Create("BaseContext", typeof(object), typeof(CompletedTodoTemplate), null, propertyChanged: OnParentContextPropertyChanged);

        public object BaseContext
        {
            get { return GetValue(BaseContextProperty); }
            set { SetValue(BaseContextProperty, value); }
        }

        public CompletedTodoTemplate ()
    {
      InitializeComponent ();
    }
        private static void OnParentContextPropertyChanged(BindableObject bindable, object oldValue, object newValue)
        {
            if (newValue != oldValue && newValue != null)
            {
                (bindable as CompletedTodoTemplate).BaseContext = newValue;
            }
        }
    }

 

Step 2

Add a name to the page containing the list view.

x:Name="TodoPage"

Inside the view containing the list view, define the data templates as resources for that parent view, pass the page’s Binding context to the templates.

<Grid.Resources>
            <ResourceDictionary>
                <DataTemplate x:Key="CurrentTodoDataTemplate">
                    <dataTemplates:CurrentTodoTemplate BaseContext="{Binding BindingContext, Source={x:Reference TodoPage}}"/>
                </DataTemplate>
                <DataTemplate x:Key="CompletedTodoDataTemplate">
                    <dataTemplates:CompletedTodoTemplate BaseContext="{Binding BindingContext, Source={x:Reference TodoPage}}"/>
                </DataTemplate>
                <dataTemplateSelector:TodoTemplateSelector x:Key="TemplateSelector"
                                                           PrimaryItemTemplate="{StaticResource CurrentTodoDataTemplate}"
                                                           SecondaryItemTemplate="{StaticResource CompletedTodoDataTemplate}"/>
            </ResourceDictionary>
        </Grid.Resources>

Step 3

Name the View cell

x:Name="Template"

Call the BaseContext’s command in the appropriate place, in our case it is the context menu delete action.

<ViewCell.ContextActions>
        <MenuItem Command="{Binding BaseContext.DeleteTodoCommand, Source={x:Reference Template}}" 
                  CommandParameter="{Binding .}"  x:Name="DeleteMenuItem" Text="Delete"/>
    </ViewCell.ContextActions>

With this, you should have completed the todo part of the tutorial and implemented the desired functionalities. Here is a view of the sample.

List View Grouping

Some times, you need to group data in to specific categories and have a means  to go quickly to each portion of that data with just a click. This is where grouping comes to your rescue. A good scenario to depict this is when you go through your phone’s contacts, you need to quickly navigate through an ordered list of contacts. Here is a simple implementation of this in Xamarin.Forms using the list view.

  1. Grouping is simply seen as a list of lists, so you need a custom implementation of the list which you will add to a main list and bind to the view
  2. You should enable grouping on your list view
  3. Define the group names and short name and the lists in the view model

Step 1

public class ListViewGrouping<T> : List<T>
    {
        public string Title { get; set; }
        public string ShortName { get; set; }

        public ListViewGrouping(string title, string shortName)
        {
            Title = title;
            ShortName = shortName;
        }
    }

 

Step 2

<ListView x:Name="MyListView"
                  ItemsSource="{Binding AllContacts}"
                  SelectedItem="{Binding SelectedContact}"
                  GroupDisplayBinding="{Binding Title}"
                  GroupShortNameBinding="{Binding ShortName}"
                  RowHeight="50"
                  IsGroupingEnabled="True">
            

            <ListView.GroupHeaderTemplate>
                <DataTemplate>
                    <ViewCell>
                        <Label Margin="10, 0,0,0" Text="{Binding Title}" FontSize="{StaticResource LargeSize}"/>
                    </ViewCell>
                </DataTemplate>
            </ListView.GroupHeaderTemplate>

            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <StackLayout Margin="10, 0,0,0">
                            <Label Text="{Binding Name}" FontSize="{StaticResource MediumSize}"
                   Style="{DynamicResource ListItemDetailTextStyle}" />
                            <Label Text="{Binding Email}" FontSize="{StaticResource LittleSize}"
                   Style="{DynamicResource ListItemTextStyle}"/>
                        </StackLayout>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>

        </ListView>

Step 3

After going through this tutorial, the demo app should be as follows.

Go through the app’s source code on github, follow this tutorial while going through the app and it will be easy to master what was said here. Get back to this post in case you need more information about the tutorial, or about the app.

This post will help you understand the Todo page and View model part of this post better

Check some other very useful Xamarin Posts here.

office 365 16% reduction on signup.
office 365 16% reduction on signup.

Special Offer

Save 16% on your Microsoft Office 365 subscription        Learn More >>

 

Follow me on social media and stay updated

If you liked this post, or it was useful to you, please 👍 like it, share it on twitter, facebook or other social media… in case you want to get updated on any new useful post, follow me on twitter  and like my page on facebook or accept notifications from this page.

Follow me on social media and stay updated

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.