April 2019

Volume 34 Number 4

[Xamarin]

What’s New in Xamarin.Forms 4.0

By Alessandro Del

During the Connect(); conference in November 2018, Microsoft unveiled the first public preview of Xamarin.Forms 4.0, the latest version of the popular development technology that makes it simple to create shared UIs across different OSes, including Android, iOS and Windows 10. Updated previews have followed, giving developers a closer look at the progress of this major release. The most important new features that make Xamarin.Forms 4.0 amazing can be summarized as follows.

Shell is a container for applications that provides most of the common features the UI needs. It represents a single place to describe the visual structure of an application and includes a common UI for page navigation; a navigation service with deep linking; and an integrated search handler. For now, the Shell will be available only for iOS and Android.

CollectionView is a completely new control created to work with lists of data in a more efficient and performant way. It’s also the foundation for the updated version of the CarouselView control.

Visual is the name of a new property that some views can use to render controls based on a given design, making it easy to create consistent UIs across platforms.

You’ll find a thorough discussion of the Shell feature in the Connect(); Special Issue (msdn.com/magazine/mt848639), so I won’t cover it here. Instead, I’ll focus on CollectionView, CarouselView, Visual and other miscellaneous improvements to the existing code base. I’ll also take a quick look at some new features included in Visual Studio 2019 Preview 2 for Xamarin.Forms. Keep in mind that the bits described in this article are still in preview and may be subject to change.

Creating and Setting Up a Project

To work with the current preview of Xamarin.Forms 4.0, you first need to create a Mobile App (Xamarin.Forms) project in Visual Studio 2017, as you normally would. Select the iOS and Android platforms and the .NET Standard code sharing strategy. When the new project is ready, with the NuGet package manager make sure you upgrade the Xamarin.Forms library to the latest pre-release available (at the time of this writing, it’s called 4.0.0.94569-pre3). When you install this preview, NuGet will also add the Xamarin.iOS.MaterialComponents package to the iOS platform project, as I’ll discussed later when talking about the Visual property.

The last step consists of enabling all the experimental features in the Xamarin.Forms 4.0 preview inside the MainActivity.cs file for Android and the AppDelegate.cs file for iOS. In both files, add the following line of code before the invocation of the Xamarin.Forms.Forms.Init method:

global::Xamarin.Forms.Forms.SetFlags("Shell_Experimental", "Visual_Experimental",
  "CollectionView_Experimental");

You should add the Shell_Experimental flag if you want to experiment with the Shell feature; otherwise, it’s optional for the next topics.

Working with Data: The CollectionView Control

Xamarin.Forms 4.0 introduces a brand-new control called CollectionView, which you can use to display and edit lists of data. From an architectural point of view, CollectionView has a couple of major benefits. First, unlike with ListView, the data templates no longer rely on Cell views, which simplifies the visual tree. As a consequence, and as the second benefit, you code CollectionView objects using the same familiar approach based on XAML, but with much simpler code. Moreover, the CollectionView control makes it simpler to manage the layout of your lists by supporting horizontal layouts and grid views. Let’s start by creating some sample data that will be displayed inside a CollectionView control. In the .NET Standard project, add the class shown in Figure 1, which represents the simplified definition of a product.

Figure 1 A Simplified Implementation of a Product Class

public class Product : INotifyPropertyChanged
{
  public event PropertyChangedEventHandler PropertyChanged;
  private void OnPropertyChanged([CallerMemberName] string propertyName = null)
  {
    PropertyChanged?.Invoke(this,
      new PropertyChangedEventArgs(propertyName));
  }
  private int _productQuantity;
  private string _productName;
  private string _productImage;
  public int ProductQuantity
  {
    get
    {
      return _productQuantity;
    }
    set
    {
      _productQuantity = value;
      OnPropertyChanged();
    }
  }
  public string ProductName
  {
    get
    {
      return _productName;
    }
    set
    {
      _productName = value;
      OnPropertyChanged();
    }
  }
  public string ProductImage
  {
    get
    {
      return _productImage;
    }
    set
    {
      _productImage = value;
      OnPropertyChanged();
    }
  }
}

Notice how the Product class implements the INotifyProperty­Changed interface to support change notification if you decide to make a product instance editable. The next step is defining a view model that exposes the product information, which will be data-bound to the UI. This is shown in Figure 2.

Figure 2 The ProductViewModel Class Exposes Data Information to the UI

public class ProductViewModel
{
  public ObservableCollection<Product> Products { get; set; }
  public ProductViewModel()
  {
    // Sample products
    this.Products = new ObservableCollection<Product>();
    this.Products.Add(new Product { ProductQuantity = 50, ProductName = "Cheese",
                                    ProductImage = "Cheese.png" });
    this.Products.Add(new Product { ProductQuantity = 10, ProductName = "Water",
                                    ProductImage = "Water.png" });
    this.Products.Add(new Product { ProductQuantity = 6, ProductName = "Bread",
                                    ProductImage = "Bread.png" });
    this.Products.Add(new Product { ProductQuantity = 40, ProductName = "Tomatoes",
                                    ProductImage = "Tomato.png" });
  }
}

In a real-world scenario, data could come from a database or from a Web service, but in this case I create a few products directly in the code for demonstration purposes. The image files used in Figure 2 can be found in the companion code for the article. In the MainPage.xaml file of your project, you can then declare a CollectionView control to display the data. The key points are assigning to the ItemsSource property the collection you want to display, and defining a data template for each item in the list. Figure 3 provides an example.

Figure 3 Declaring a CollectionView to Display Data

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="https://xamarin.com/schemas/2014/forms"
             xmlns:x="https://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:XF4_WhatsNew"
             x:Class="XF4_WhatsNew.MainPage">
  <StackLayout>
    <CollectionView x:Name="ProductList" ItemsSource="{Binding Products}">
      <CollectionView.ItemTemplate>
        <DataTemplate>
          <Grid>
            <Grid.ColumnDefinitions>
              <ColumnDefinition Width="24"/>
              <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <Image Source="{Binding ProductImage}" HeightRequest="22" />
            <StackLayout Grid.Column="1" Orientation="Vertical" Spacing="5">
              <Label Text="{Binding ProductName}" FontSize="Medium"
                TextColor="Blue"/>
              <Label Text="{Binding ProductQuantity}" FontSize="Small"
                TextColor="Black"/>
            </StackLayout>
          </Grid>
        </DataTemplate>
      </CollectionView.ItemTemplate>
    </CollectionView>
  </StackLayout>
</ContentPage>

Declaring CollectionView and assigning its ItemTemplate property with a DataTemplate object in XAML works the same as with the ListView control, with the important difference being that you don’t have to deal with Cell views anymore. You then create an instance of the ProductViewModel class and assign it to the page’s BindingContext property so that data binding can work, like in the following code to be written in the MainPage.xaml.cs file:

public partial class MainPage : ContentPage
{
  private ProductViewModel ViewModel { get; set; }
  public MainPage()
  {
    InitializeComponent();
    this.ViewModel = new ProductViewModel();
    this.BindingContext = this.ViewModel;
  }
}

If you run this code, you’ll see a result like that in Figure 4.

Displaying Lists of Data with CollectionView
Figure 4 Displaying Lists of Data with CollectionView

As you’ll see, CollectionView provides other interesting properties for implementing different layouts and handling item selection.

Orientation and Layout If you have experience with the ListView control in Xamarin.Forms, you know that there’s no built-in option to set the orientation as horizontal. This can certainly be accomplished using custom renderers, but it increases your code complexity. Similarly, there’s no built-in option to change the layout from a list view to a grid view. The CollectionView control, however, makes setting different layout options extremely simple via the ItemsLayout property, which can be assigned with properties from the ListItemsLayout class, such as VerticalList and HorizontalList. By default, the ItemsLayout property is assigned with ListItemsLayout.VerticalList, so you don’t need to assign it explicitly for a vertical layout. But to implement a horizontal orientation, add the following code snippet to your XAML:

<CollectionView ItemsSource="{Binding Products}"
  It­emsLayout="{x:Static ListItemsLayout.HorizontalList}">
  ...
</CollectionView>

Actually, the ItemsLayout property also allows you to define a grid view. In this case, you use the GridItemsLayout class instead of ListItemsLayout, as in the following example that demonstrates how to create a horizontal grid view:

<CollectionView x:Name="ProductList" ItemsSource="{Binding Products}" >
  <CollectionView.ItemsLayout>
    <GridItemsLayout Orientation="Horizontal" Span="3" />
  </CollectionView.ItemsLayout>
    ...
</CollectionView>

By default, the GridItemsLayout shows items in a single row with the horizontal layout, so you can set the Span property to decide how many rows the grid will contain. As items are added to the data-bound list, the grid view will grow horizontally. You can get a vertical grid view with the following code:

<CollectionView x:Name="ProductList" ItemsSource="{Binding Products}" >
  <CollectionView.ItemsLayout>
    <GridItemsLayout Orientation="Vertical" Span="2" />
  </CollectionView.ItemsLayout>
    ...
</CollectionView>

Similarly, a vertical grid will display items in a single column by default, so you can change the Span properties and specify the number of columns for the grid.

Managing Item Selection The CollectionView control allows for selecting single or multiple items, and for disabling selection. The SelectionMode property can be assigned values from the Xamarin.Forms.SelectionMode enumeration: None, Single and Multiple. With None, you can disable item selection very easily. The SelectedItem property, of type object, represents one or more selected items in the list. The SelectionChanged event is then raised when the user selects an item in the CollectionView. Such an event will only work if SelectionMode has been assigned with either Single (which is also the default if SelectionMode isn’t specified) or Multiple. So, in your XAML you might have the following assignments:

<CollectionView x:Name="ProductList" ItemsSource="{Binding Products}"
  SelectionMode="Single" SelectionChanged="ProductList_SelectionChanged">
...
</CollectionView>

Then the SelectionChanged event handler allows you to manage item selection via a parameter of type Xamarin.Forms.Selection­ChangedEventArgs. This object exposes two useful properties, CurrentSelection and PreviousSelection. As the names imply, the first property returns the currently selected objects while the second property returns objects that were selected previously. This is very useful, because in some situations you might need to know what object instances were actually selected before the new selection. Both properties are of type IReadOnlyList<object> and expose the list of selected items. If SelectionMode is set as Single, you can retrieve the instance of the selected item as follows:

private void ProductList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
  var selectedProduct = this.ProductList.SelectedItem as Product;
  var singleProduct = e.CurrentSelection.FirstOrDefault() as Product;
}

Whether you use the CollectionView.SelectedItem property or the first item in the collection, you need to cast the resulting object into the expected type, Product in this case. If SelectionMode is set as Multiple, you first need to cast the read-only collection of selected items into a list of objects of the expected type, as in the following:

private void ProductList_SelectionChanged(object sender,
  SelectionChangedEventArgs e)
{
  var selectedItems = e.CurrentSelection.Cast<Product>();
  foreach(var product in selectedItems)
  {
    // Handle your object properties here...
  }
}

The same approach would work for the SelectionChanged­EventArgs.PreviousSelection property for both single- and multiple-­item selection. As a modern view, CollectionView also provides support for the Model-View-ViewModel (MVVM) pattern when handling item selection, so you can use the SelectionChangedCommand property, which can be bound to an instance of the Command class in your view model; and the SelectionChangedCommandParameter, which allows for passing a parameter to the command. For example, let’s extend the ProductViewModel class in order to add a SelectedProduct property that will be data-­bound to the CollectionView’s SelectedItem property, and with a new command for selection handling. For the sake of simplicity, the property of type Command will use neither the generic imple­mentation nor the command parameter. Figure 5 shows what the view model looks like.

Figure 5 Extending the View Model with MVVM Support

public class ProductViewModel: INotifyPropertyChanged
{
  public ObservableCollection<Product> Products { get; set; }
  private Product _selectedProduct;
  public Product SelectedProduct
  {
    get
    {
      return _selectedProduct;
    }
    set
    {
      _selectedProduct = value;
      OnPropertyChanged();
    }
  }
  private Command _productSelectedCommand;
  public Command ProductSelectedCommand
  {
    get
    {
      return _productSelectedCommand;
    }
      set
      {
        _productSelectedCommand = value;
        OnPropertyChanged();
      }
    }
  public ProductViewModel()
  {
    // Sample products
    this.Products = new ObservableCollection<Product>();
    this.Products.Add(new Product { ProductQuantity = 50,
      ProductName = "Cheese", ProductImage = "Cheese.png" });
    this.Products.Add(new Product { ProductQuantity = 10,
      ProductName = "Water", ProductImage = "Water.png" });
    this.Products.Add(new Product { ProductQuantity = 6,
      ProductName = "Bread", ProductImage = "Bread.png" });
    this.Products.Add(new Product { ProductQuantity = 40,
      ProductName = "Tomatoes", ProductImage = "Tomato.png" });
    this.ProductSelectedCommand =
      new Command(ExecuteProductSelectedCommand,
        CanExecuteProductSelectedCommand);
  }
  private bool CanExecuteProductSelectedCommand(object arg)
  {
    return this.SelectedProduct != null;
  }
  private void ExecuteProductSelectedCommand(object obj)
  {
    // Handle your object here....
    var currentProduct = this.SelectedProduct;
  }
  public event PropertyChangedEventHandler PropertyChanged;
  private void OnPropertyChanged([CallerMemberName] string propertyName = null)
  {
    PropertyChanged?.Invoke(this,
      new PropertyChangedEventArgs(propertyName));
  }
}

Then, in your XAML, assign the selection properties as follows:

<CollectionView x:Name="ProductList" ItemsSource="{Binding Products}"
                SelectedItem="{Binding SelectedProduct, Mode=TwoWay}"
                SelectionChangedCommand="{Binding ProductSelectionCommand}"
                SelectionMode="Single">
</CollectionView>

With this approach, you can work against your selected objects in the view model, which is the place where you implement the business logic, keeping the work on the data decoupled from the views.

Managing Empty Lists One of the most important features offered by CollectionView, and definitely one of my favorites, is the built-in option to display a different view if the data-bound list is empty, at no cost. This is possible via the EmptyView property, which allows for specifying the view that will be displayed in the case of empty data. It works like this:

<CollectionView>
...           
  <CollectionView.EmptyView>
     <Label Text="No data is available" TextColor="Red"
       FontSize="Medium"/>
  </CollectionView.EmptyView>
</CollectionView>

This code produces the result shown in Figure 6. With this ­approach, you don’t need to create a data template for empty states or to implement data template selectors.

Providing a View for Empty States
Figure 6 Providing a View for Empty States

As an alternative, you can use the EmptyViewTemplate property if you want to implement a custom view. For instance, the following code demonstrates how to display both an image and a text message with empty lists:

<CollectionView.EmptyViewTemplate>
  <DataTemplate>
    <StackLayout Orientation="Vertical" Spacing="20">
      <Image Source="EmptyList.png" Aspect="Fill"/>
      <Label Text="No data is available" TextColor="Red"/>
    </StackLayout>
  </DataTemplate>
</CollectionView.EmptyViewTemplate>

ScrollingCollectionView also exposes the ScrollTo method, which you can use to programmatically scroll the list to a specified item. The following scrolls to the third item in the list:

 

ProductList.ScrollTo(2);

Simpler API Surface The CollectionView control exposes a simpler API surface than ListView. For instance, CollectionView has no properties to work with separators (such as SeparatorVisibility and SeparatorColor), so you can implement your own separators with views like the BoxView in the data template. CollectionView doesn’t implement a property like ListView.RowHeight, because now an item’s height is determined by the first item in the list. The HasUnevenRows property in ListView has a counter­part in CollectionView called ItemSizingStrategy. With regard to pull-to-refresh techniques, properties such as IsPullToRefreshEnabled, IsRefreshing and RefreshCommand, which are exposed by ListView, aren’t available in the CollectionView control. However, Microsoft is working on implementing properties that support views for refresh states. The Xamarin.Forms.CollectionView Spec document on GitHub (bit.ly/2N6iw8a) provides a detailed explanation about refresh options and also about other API implementations, including architectural decisions.

Introducing CarouselView

The CarouselView control isn’t new in Xamarin.Forms 4.0. However, it has been completely rebuilt based on the CollectionView structure and performance.

With CarouselView, you can show one object in the list at a time, in a card format. Figure 7, taken from the official Xamarin blog (bit.ly/2BwWMNV), shows how CarouselView provides a way to swipe between items.

The CarouselView Shows Items in the Form of Cards, Enabling the Swipe Gesture
Figure 7 The CarouselView Shows Items in the Form of Cards, Enabling the Swipe Gesture

With regard to the sample code described previously, you could use the CarouselView to display the list of products as cards, as shown in Figure 8.

Figure 8 Displaying Items as Cards with CarouselView

<CarouselView x:Name="ProductList" ItemsSource="{Binding Products}">
  <CarouselView.ItemsLayout>
    <GridItemsLayout Orientation="Horizontal"
                     SnapPointsAlignment="Center"
                     SnapPointsType="Mandatory"/>
  </CarouselView.ItemsLayout>
  <CarouselView.ItemTemplate>
    <DataTemplate>
      <Grid>
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="24"/>
          <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Image Source="{Binding ProductImage}" HeightRequest="22" />
        <StackLayout Grid.Column="1" Orientation="Vertical"
                     Spacing="5">
          <Label Text="{Binding ProductName}" FontSize="Medium"
                 TextColor="Blue"/>
          <Label Text="{Binding ProductQuantity}" FontSize="Small"
                 TextColor="Black"/>
        </StackLayout>
      </Grid>
    </DataTemplate>
  </CarouselView.ItemTemplate>
  <CarouselView.EmptyView>
    <Label Text="No data is available" TextColor="Red"
           FontSize="Medium"/>
  </CarouselView.EmptyView>
</CarouselView>

Let’s focus on the key points in the code.

Declaring CarouselView Declaring CarouselView in XAML is very similar to how you declared CollectionView. You still need to bind the ItemsSource property with the collection of data you want to display. Notice how you can also provide views for empty states, exactly as you did with CollectionView.

Orientation CarouselView supports both horizontal and vertical orientation. You assign the ItemsLayout property with an instance of GridItemsLayout, passing either Horizontal or Vertical to the Orientation property.

Adding Snap Points With the SnapPointsAlignment and SnapPointsType properties on the ItemsLayout, you can configure the behavior of the card. For example, in Figure 8 the SnapPointsType and SnapPointsAlignment properties make sure the card appears centered. Without those assignments, the card would stop part way between the other cards. Allowed values for SnapPointsAlignment are Center, End and Start. Allowed values for SnapPointsType are Mandatory, MandatorySingle and None.

As with CollectionView, keep in mind that the final bits might provide a different API implementation. With the current preview, you might also experience an InvalidCastException when the app is rendering the view. This is an unstable build, so such problems are to be expected.

Creating Consistent UIs with the Visual Property

Xamarin.Forms 4.0 introduces a new way for developers to create UIs that look largely identical on both Android and iOS with almost no effort. The key is a new property called Visual, exposed by the VisualElement class. The Visual property makes sure that new, additional renderers are used to draw visual elements instead of the default renderer. In its current state of preview, Xamarin.Forms 4.0 includes an experimental rendering mechanism based on material design, currently available for the following views: Button, Entry, Frame and ProgressBar. You can enable the new visual rendering like this:

 

<Entry Visual="Material" />

Figure 9, taken from the Microsoft release notes for Xamarin.Forms 4.0 (bit.ly/2BB4MxA), shows an example of a consistent UI across Android and iOS, created using Visual.

Consistent UI with the Visual Property
Figure 9 Consistent UI with the Visual Property

You could also assign the Visual property at the ContentPage level to make sure that any of the supported views in your code will use the specified design. In order to use Visual, your project must satisfy a few requirements:

  • In the iOS project, the Xamarin.iOS.MaterialComponents NuGet package must be installed. However, this is automatically installed when you upgrade your Xamarin.Forms package to 4.0.
  • For Android, your project must be based on API 29 and the support libraries must use version 28. Additionally, the app theme must inherit from one of the Material Components themes.

It’s worth noting that the development of Visual has really just begun, so developers can reasonably expect many updates. In general, it’s an extremely useful addition that will save you lots of time when you need to create UIs that are identical across platforms.

Miscellaneous Improvements

In Xamarin.Forms 4.0, you can now specify a color for the spinner when pull-to-refresh is enabled. This is accomplished by assigning the RefreshControlColor property as follows:

<ListView RefreshControlColor="Green"/>

Additionally, in ListView it’s possible to hide the scrollbars without writing custom renderers, by simply assigning the HorizontalScrollBarVisibility and VerticalScrollBarVisibility properties:

<ListView HorizontalScrollBarVisibility="Always"
  VerticalScrollBarVisibility="Never"/>

Possible values are Always (always visible), Never (never visible) and Default (based on the target platform’s default).  Also, the Editor control now includes support for text prediction, as in the Entry control. You can simply assign the IsTextPredictionEnabled property with True or False, like so:

<Editor IsTextPredictionEnabled="True" />

With Xamarin.Forms 4.0, the SwitchCell view receives the OnColor property, just as Switch view did in the previous major releases. So, you can now set a different color for the active state when using SwitchCell in your data templates:

<SwitchCell OnColor="Red"/>

These are the most interesting improvements from a practical point of view, but the full list of updates and issues fixed is available on the release notes page and will be eventually updated once Xamarin.Forms 4.0 is finally released to production.

A Quick Look at Xamarin.Forms in Visual Studio 2019

Microsoft recently released Visual Studio 2019 Preview 3 (bit.ly/2QcOK6z), which provides an early look at what’s coming with the next major release of Visual Studio, including support for Xamarin.Forms 4.0. For example, a new project template called Shell is now available in the New Cross Platform App dialog (see Figure 10). This template generates a new project based on the Shell feature, with several views referenced in the Shell object in the main page’s XAML code.

The New Project Template Based on the Shell
Figure 10 The New Project Template Based on the Shell

Visual Studio 2019 also brings to Xamarin.Forms a new Properties panel, shown in Figure 11, which was already available to other development platforms. It displays property values as soon as you click on a view in your XAML code or in the Xamarin.Forms Previewer.

The New Properties Panel
Figure 11 The New Properties Panel

If you change property values through the Properties panel, the XAML code is automatically updated to reflect your changes. Notice how specific editors are available for some types, such as the color editor for properties of type Xamarin.Forms.Color.

Wrapping Up

As with the previous major releases, Xamarin.Forms 4.0 demonstrates the huge investment Microsoft is making to improve the development experience not only with new features, but also by making the existing tools and code base more and more reliable, which is what developers need in their real-world projects. It will be interesting to see what further news is presented at the upcoming Microsoft Build conference, which will take place in Seattle May 6-8.


Alessandro Del Sole has been a Microsoft MVP since 2008 and a Xamarin Certified Developer. He has authored many books, eBooks, instructional videos and articles about .NET development with Visual Studio. Del Sole works for Fresenius Medical Care as a senior software engineer, focusing on building .NET and mobile apps with Xamarin in the health care market. You can follow him on Twitter: @progalex.

Thanks to the following Microsoft technical expert for reviewing this article: Paul DiPietro


Discuss this article in the MSDN Magazine forum