Advanced Tutorial on ListViews with Xamarin.Forms with MVVM and Reac

in #utopian-io6 years ago (edited)

In this tutorial, we will cover the advanced techniques for displaying data and Full MVVM interaction with Xamarin.Forms ListView and ReactiveUI

This is what you will learn from this tutorial.

  • 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

Requirements

  • Xamarin.Forms project template on Visual studio or Xamarin Studio
  • Install ReactiveUI's Nuget package

Difficulty

  • Advanced

Tutorial

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.

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.

(html comment removed:  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.

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.
Pass the Binding context from xaml to the data template
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.

Step1

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;
            }
        }
    }

Step2

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.

(html comment removed:  Place the name on the page dont forget )
x:Name="TodoPage"

<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>

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

UWPDemo2.gif
UWPDemo.gif



Posted on Utopian.io - Rewarding Open Source Contributors

Sort:  

Your contribution cannot be reviewed because it does not follow the Utopian Rules, and is considered as plagiarism. Plagiarism is not allowed on Utopian, and posts that engage in plagiarism will be flagged and hidden forever.

Need help? Write a ticket on https://support.utopian.io.

Chat with us on Discord.

[utopian-moderator]

https://doumer.me/xamarin-forms-listview-advanced-guide-with-mvvm/
Is My Blog, and I'm the one who wrote the post there a while ago.

Can you put the link of the post to your blog to prove it? There was a rule about reposting already posted things, but with the new update, it's gone it seems. Sorry for the misconception.

Yes of course I can, here is the link to the post on my blog:
https://doumer.me/xamarin-forms-listview-advanced-guide-with-mvvm/
And You can also verify if it is my blog by looking at my User Name on steemit and the blog's domain name you will see that they are similar, and also check my blog's about page and you will see my picture as here on steemit and my bio, here is teh link: https://doumer.me/about/

Sorry for misconception. I use an extension to block ads, so I didn't see your social platforms at bottom. I'll review your tutorial when I have spare time. Don't worry, it won't be late than tomorrow.

Hey @yokunjon, I just gave you a tip for your hard work on moderation. Upvote this comment to support the utopian moderators and increase your future rewards!

Coin Marketplace

STEEM 0.16
TRX 0.15
JST 0.029
BTC 58127.19
ETH 2452.98
USDT 1.00
SBD 2.36