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.
<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.
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.
Posted on Utopian.io - Rewarding Open Source Contributors