# Rotating a list in Xamarin.Forms using CollectionView

One of the advantages of using a [CollectionView](https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/collectionview/introduction) rather than a ListView is the ability to change the layout and orientation of the items in the collection. In this article I look at how we can use this feature to improve the look of our Xamarin.Forms apps in Portrait and Landscape orientation. The code for this article is available on GitHub: [irongut/RotateCollectionDemo](https://github.com/irongut/RotateCollectionDemo)

## Page Layout

I started this as an experiment for one of my existing Xamarin.Forms apps so I created a slightly simplified copy of its page layout with different icons and text. With Halloween 🎃 approaching I searched [FlatIcon](https://www.flaticon.com/authors/freepik) for suitably spooky images 👻 and copied some ghoulish text 🧟‍♂️ from [Zombie Ipsum](http://www.zombieipsum.com/). I also implemented a dark theme, using my favourite colour palette [Nord](https://www.nordtheme.com/).

![Portrait Screenshot](https://cdn.hashnode.com/res/hashnode/image/upload/v1635108935747/MlYAilkxU.png)

In the original app the list of items is a `ListView` which only looks good in Portrait mode, I want to change it to a `CollectionView` and respond to the user rotating the device so the page always looks great.

## Data

I'm only really interested in the UI so I'm using a simple `DataItem` model with string properties for the text and the embedded resource URIs of the SVG images.

```csharp
public class DataItem
{
    private string name;
    public string Name { get => name; set => SetProperty(ref name, value); }

    private string firstLine;
    public string FirstLine { get => firstLine; set => SetProperty(ref firstLine, value); }

    private string secondLine;
    public string SecondLine { get => secondLine; set => SetProperty(ref secondLine, value); }

    private string firstImage;
    public string FirstImage { get => firstImage; set => SetProperty(ref firstImage, value); }

    private string secondImage;
    public string SecondImage { get => secondImage; set => SetProperty(ref secondImage, value); }

    protected bool SetProperty<T>(ref T field, T newValue)
    {
        if (!Equals(field, newValue))
        {
            field = newValue;
            return true;
        }

        return false;
    }
}
```

The constructor of `MainViewModel` generates a random list of `DataItem`, adding them to `Data` - an `ObservableCollection` which is bound to `CollectionView.ItemsSource`.

```csharp
public ObservableCollection<DataItem> Data { get; }

public MainViewModel()
{
	Data = new ObservableCollection<DataItem>();
	GenerateData();
}

private void GenerateData()
{
    Data.Clear();
    for (int i = 0; i < random.Next(6, 10); i++)
    {
        DataItem item = new DataItem();
        if (random.Next(2) < 1)
        {
            item.Name = "Zombie Boy";
            item.FirstLine = "Pestilentia est plague haec";
            item.SecondLine = "Summus brains sit";
            item.FirstImage = "resource://RotateCollectionDemo.Resources.zombie.boy.svg";
        }
        else
        {
            item.Name = "Zombie Girl";
            item.FirstLine = "Decaying ambulabat mortuos";
            item.SecondLine = "Apathetic malus voodoo";
            item.FirstImage = "resource://RotateCollectionDemo.Resources.zombie.girl.svg";
        }
        item.SecondImage = random.Next(2) < 1
            ? "resource://RotateCollectionDemo.Resources.zombie.hand.svg"
            : "resource://RotateCollectionDemo.Resources.pumpkin.svg";
        Data.Add(item);
    }
}
```

## Detecting Device Orientation

In order to change the layout of a page based on device orientation we need to know the initial orientation and the new orientation after the user rotates their device.

[Xamarin.Essentials](https://docs.microsoft.com/en-us/xamarin/essentials/) `DeviceDisplay.MainDisplayInfo` contains a `Rotation` property and `OnMainDisplayInfoChanged` event that is triggered whenever any screen metric changes. This seems like the obvious choice but unfortunately the event is unreliable on Android, often returning the previous orientation; see [xamarin/Essentials#1355](https://github.com/xamarin/Essentials/issues/1355).

A more reliable alternative is to override the `Page.OnSizeAllocated` method, check if the width or height have changed and call a ViewModel method to update the UI. Using `OnSizeAllocated` handles both initial orientation and when the user rotates their device. This app only has one page so I've implemented `OnSizeAllocated` in the code behind, in a normal project I would create a base page which overrides `OnSizeAllocated` and a base ViewModel with an abstract `SetLayout()` method.

```csharp
public partial class MainPage : ContentPage
{
    private double width;
    private double height;

    private readonly MainViewModel vm = new MainViewModel();

    public MainPage()
    {
        InitializeComponent();
        BindingContext = vm;
    }

    protected override void OnSizeAllocated(double width, double height)
    {
    	base.OnSizeAllocated(width, height);
        if (this.width != width || this.height != height)
        {
        	this.width = width;
        	this.height = height;
			vm.SetLayout();
		}
	}
}
```

In the ViewModel I use Xamarin.Essentials `DeviceDisplay.MainDisplayInfo` to determine if the device is in Portrait or Landscape orientation by comparing the `Width` and `Height` of the screen.

```csharp
public void SetLayout()
{
	DisplayInfo displayInfo = DeviceDisplay.MainDisplayInfo;
	if (displayInfo.Width > displayInfo.Height)
	{
		// landscape
    }
    else
    {
    	// portrait
    }
}
```

That updates the UI when the user rotates their device but what about when they resume the app from the background? To ensure the page updates on resume I've overridden the `Application.OnResume` method in _App.xaml.cs_ and used the Xamarin.Forms [MessagingCenter](https://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/messaging-center) to send an `AppOnResume` message. The `MessagingCenter` class implements a publish-subscribe pattern with weak references, allowing loosely coupled message-based communication between components.

```csharp
protected override void OnResume()
{
	MessagingCenter.Send(this, "AppOnResume");
}
```

My ViewModel subscribes to the `AppOnResume`message and calls `SetLayout()` when it receives the message.

```csharp
public void OnAppearing()
{
	MessagingCenter.Subscribe<App>(this, "AppOnResume", (_) => SetLayout());
}

public virtual void OnDisappearing()
{
	MessagingCenter.Unsubscribe<App>(this, "AppOnResume");
}
```

## Rotating the CollectionView

Once we can detect device orientation changes we can update the UI. In this example I want my `CollectionView` to be a vertical list for Portrait orientation and a horizontal list for Landscape orientation. Creating a data binding on `CollectionView.ItemsLayout` was a bit tricky but after some trial and error I realised I needed to create a `LinearItemsLayout` object for each orientation when the page is created and switch between them when the user rotates their device.

```csharp
private readonly ItemsLayout portLayout = new LinearItemsLayout(ItemsLayoutOrientation.Vertical) { ItemSpacing = 15 };
private readonly ItemsLayout landLayout = new LinearItemsLayout(ItemsLayoutOrientation.Horizontal) { ItemSpacing = 15 };

private ItemsLayout collectionLayout;
public ItemsLayout CollectionLayout { get => collectionLayout; set => SetProperty(ref collectionLayout, value); }
```

Just switching the layout isn't enough for a great user experience, my vertical list items would look wrong forced into a horizontal list so I want to change the item template as well. The easiest way to do this is to include a data template for each orientation with a data binding on `CollectionView.ItemTemplate` to switch between them.

```csharp
private DataTemplate collectionTemplate;
public DataTemplate CollectionTemplate { get => collectionTemplate; set => SetProperty(ref collectionTemplate, value); }

public void SetLayout()
{
	DisplayInfo displayInfo = DeviceDisplay.MainDisplayInfo;
	if (displayInfo.Width > displayInfo.Height)
	{
		// landscape
		CollectionTemplate = App.Current.Resources.TryGetValue("landTemplate", out object value)
			? (DataTemplate)value
			: throw new Exception("landTemplate not found!");
		CollectionLayout = landLayout;
	}
	else
	{
		// portrait
		CollectionTemplate = App.Current.Resources.TryGetValue("portTemplate", out object value)
			? (DataTemplate)value
			: throw new Exception("portTemplate not found!");
		CollectionLayout = portLayout;
	}
}
```

The data templates can be found in _Styles\DefaultStyles.xaml_ and are added to the application resource dictionary in _App.xaml.cs_.

![Landscape Screenshot](https://cdn.hashnode.com/res/hashnode/image/upload/v1635108969412/UveOC5Zqp.png)

## Final Thoughts

Using CollectionView we can easily update the layout of list based pages when the user rotates their device and improve the user experience of our apps. I haven't tested this with .Net MAUI yet but it should work with minimal changes.

For larger screen devices like tablets we could use grid layouts instead of list layouts for an even better user experience.

![Combined  Screenshot](https://cdn.hashnode.com/res/hashnode/image/upload/v1635111410739/mkggdgiXU.png)

