Blank ListView Items on Android in Xamarin.Forms 4+

If you’re using ListViews and are targeting Android then read on. There are some things that may be impacting you, namely:

“ListView blank cells when CachingStrategy=”RetainElement” on Android:
https://github.com/xamarin/Xamarin.Forms/issues/6721

…which has been closed by this one:
https://github.com/xamarin/Xamarin.Forms/pull/6390

At the time of writing it looks as though this hasn’t made it into a Xamarin Forms release as yet, but even once it has, the fix is useful to know.

In a nutshell, the default behavior for ListViews is to have a CachingStrategy of RetainElement. It’s when this is set that the above issue is occurring on Android. The other options are:

RecycleElement
RecycleElementAndDataTemplate

If you want to use recycling (and you should have a good reason not to if you don’t) then you’ll want to use one of these values, depending on whether or not you’re using multiple data templates. This involves a very simple change to the ListView in xaml (you’re using xaml, right? I’m looking at you @michaelstonis ;) )

<ListView>
    <!-- Your ListView definition -->
</ListView>

…changes to…

<ListView CachingStrategy=”RecycleElement”>
    <!-- Your ListView definition -->
</ListView>

…and that’s it, unless…

Things get a little more complicated if you’re using your own version of a ListView control, which derives from the Xamarin Forms ListView. Take for example the following TappableListView control:

using System.Windows.Input;
using Xamarin.Forms;

namespace Controls
{
    public class TappableListView : ListView
    {
        public static BindableProperty ItemClickCommandProperty =
            BindableProperty.Create("ItemClickCommand", typeof(ICommand), typeof(TappableListView), null, BindingMode.Default);

        public TappableListView()
        {
            this.ItemTapped += this.OnItemTapped;
        }

        public ICommand ItemClickCommand
        {
            get { return (ICommand)this.GetValue(ItemClickCommandProperty); }
            set { this.SetValue(ItemClickCommandProperty, value); }
        }

        private void OnItemTapped(object sender, ItemTappedEventArgs e)
        {
            if (e.Item != null && this.ItemClickCommand != null && this.ItemClickCommand.CanExecute(e.Item))
            {
                this.ItemClickCommand.Execute(e.Item);
            }

            this.SelectedItem = null;
        }
    }
}

…which is used like this:

    <controls:TappableListView 
        Grid.Row="3"
        Margin="10"
        ItemsSource="{Binding YourDataItems}"
        ItemClickCommand="{Binding SelectItemCommand}"
        SeparatorVisibility="None"
        HasUnevenRows="true"
        BackgroundColor="Transparent"
        >
        <controls:TappableListView.ItemTemplate>
            <DataTemplate>
                <…your template here…>
            </DataTemplate>
        </controls:TappableListView.ItemTemplate>
    </controls:TappableListView>

You’ll soon discover that you can’t just set the CachingStrategy in the same way. This is because that property is not settable – it’s read only and has to be set in the constructor, so you’ll need to set up your custom ListView control to handle this, and change your xaml to pass it in. From our previous example, that means adding a constructor parameter, and passing it through to the ListView we’re deriving from:

public TappableListView(ListViewCachingStrategy listViewCachingStrategy)
    : base(listViewCachingStrategy)
{
    this.ItemTapped += this.OnItemTapped;
}

Then to pass the required value into this from the xaml:

    <controls:TappableListView 
        Grid.Row="3"
        Margin="10"
        ItemsSource="{Binding YourDataItems}"
        ItemClickCommand="{Binding SelectItemCommand}"
        SeparatorVisibility="None"
        HasUnevenRows="true"
        BackgroundColor="Transparent"
        >
        <x:Arguments>
            <ListViewCachingStrategy>RecycleElement</ListViewCachingStrategy>
        </x:Arguments>
        <controls:TappableListView.ItemTemplate>
            <DataTemplate>
                <…your template here…>
            </DataTemplate>
        </controls:TappableListView.ItemTemplate>
    </controls:TappableListView>

…and that’s it. Not only will you have actually see all of your list items when scrolling on Android, you’ll also continue to use cell recycling properly even after this bug has been fixed by the Xam Forms team.

Happy coding.