RecyclerView TemplateSelector in MvvmCross

This is how you can show different layouts for items in a RecyclerView

Posted by Sven-Michael Stübe on June 12, 2016

There is a old tutorial from Stuart Lodge (Polymorphic lists in the MvvmCross tutorials) where he explains how to show different table cells for different types of ViewModels. This post will show you how to do the same with a MvxRecyclerView. The example code is available on github.

Problem

Sometimes there is the need to layout the items of RecyclerView differently. This is mostly the case, if your ItemsSource stores items of different classes.

ViewModels

For this example, we use the following ViewModels:

public class FirstViewModel : MvxViewModel
{
    public ObservableCollection<PetViewModelBase> Pets { get; }

    public FirstViewModel()
    {
        Pets = new ObservableCollection<PetViewModelBase>
        {
            new CatViewModel {Age = 10, LifesLeft = 6, Name = "Paul"},
            new CatViewModel {Age = 1, LifesLeft = 9, Name = "Horst"},
            new FishViewModel {Age = 1, Fins = 2, Name = "Nemo"},
            new CatViewModel {Age = 16, LifesLeft = 4, Name = "Erna"},
            new CatViewModel {Age = 5, LifesLeft = 6, Name = "Nougat"},
            new FishViewModel {Age = 12, Fins = 2, Name = "Marlin"},
            new FishViewModel {Age = 9, Fins = 5, Name = "Sharky"},
            new CatViewModel {Age = 7, LifesLeft = 1, Name = "Tirebiter"},
            new CatViewModel {Age = 8, LifesLeft = 6, Name = "Moritz"},
            new FishViewModel {Age = 2, Fins = 5, Name = "Barsch"},
            new FishViewModel {Age = 4, Fins = 20, Name = "Finny"},
            new FishViewModel {Age = 8, Fins = 3, Name = "Klaus"},
            new FishViewModel {Age = 1, Fins = 1, Name = "Pirat"},
            new CatViewModel {Age = 3, LifesLeft = 9, Name = "Mickey"}
        };
    }
}

// Items 

public abstract class PetViewModelBase
{
    public string Name { get; set; }
    public int Age { get; set; }
}

public class CatViewModel : PetViewModelBase
{
    public int LifesLeft { get; set; }
}

public class FishViewModel : PetViewModelBase
{
    public int Fins { get; set; }
}

Solution

Luckily MvvmCorss includes a neat mechanism for this since version 4.1.5. It’s called TemplateSelector and defined in the interface IMvxTemplateSelector. The purpose of this interface is to map your item to a layout id based on a rule that you can define.

Selecting by ViewModel type

In this scenario, we want to show different layouts for cats (CatViewModel) and fishes (FishViewModel).

public class TypeTemplateSelector : IMvxTemplateSelector
{
    private readonly Dictionary<Type, int> _typeMapping;

    public TypeTemplateSelector()
    {
        _typeMapping = new Dictionary<Type, int>
        {
            {typeof(CatViewModel), Resource.Layout.pet_cat},
            {typeof(FishViewModel), Resource.Layout.pet_fish}
        };
    }

    public int GetItemViewType(object forItemObject)
    {
        return _typeMapping[forItemObject.GetType()];
    }

    public int GetItemLayoutId(int fromViewType)
    {
        return fromViewType;
    }
}

Therefore we create a lookup table _typeMapping and use it in GetItemViewType that is called for each item of the list. The parameter forItemObject is the current item for which we should return the view type. We return the layout id directly, because we have one layout id per view type. If you want to know more about the purpose of view types have a look at: Adapter.getItemViewType(int).

Selecting by a property value

In our second scenario we want to show different layouts for old, adult and young pets using the Age property. We have a common base class and there is a generic base class MvxTemplateSelector<T> that allows us to implement IMvxTemplateSelector without casting manually.

public class AgeTemplateSelector : MvxTemplateSelector<PetViewModelBase>
{
    public override int GetItemLayoutId(int fromViewType)
    {
        return fromViewType;
    }

    protected override int SelectItemViewType(PetViewModelBase forItemObject)
    {
        if (forItemObject.Age <= 1)
            return Resource.Layout.pet_age_young;
        else if (forItemObject.Age <= 10)
            return Resource.Layout.pet_age_adult;
        return Resource.Layout.pet_age_old;
    }
}

This time we override SelectItemViewType that is called with a PetViewModelBase as parameter. We return the layout id directly as view type again. You can use the MvxTemplateSelector<T> in the first scenario, too.

Setting the Selector

You can set the selector in your code behind:

protected override void OnCreate(Bundle bundle)
{
    base.OnCreate(bundle);
    SetContentView(Resource.Layout.FirstView);
    
    var petList = FindViewById<MvxRecyclerView>(Resource.Id.recyclerView);
    petList.ItemTemplateSelector = new TypeTemplateSelector();
}

And you can set it in the xml. But this code is not as refactoring safe as the code behind version and you can’t initialize the selector if you create a configurable one. The attribute is called MvxTemplateSelector and has to be set with the full qualified class name of the selector followed by the assembly name.

<MvvmCross.Droid.Support.V7.RecyclerView.MvxRecyclerView
    android:id="@+id/recyclerView"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="1"
    local:MvxBind="ItemsSource Pets"
    local:MvxTemplateSelector="RecyclerViewDifferentTemplates.Droid.TypeTemplateSelector,RecyclerViewDifferentTemplates.Droid">
</MvvmCross.Droid.Support.V7.RecyclerView.MvxRecyclerView>

It is possible to exchange the selector at the runtime as well. The items will update immediately. In the sample the two buttons Type and Age are switching between our two custom selectors. Just give it a try.

Result

The Result looks like:


Background Photo by Andy Arthur / CC BY

Found a typo? Send me a pull request!