dotnet/maui · error · Exception

ItemTemplate count has exceeded the limit of {ViewTypeCount}

Error message

ItemTemplate count has exceeded the limit of {ViewTypeCount}
Please make sure to reuse DataTemplate objects

What it means

The ListViewAdapter assigns a unique integer key to each distinct DataTemplate instance it encounters via the _templateToId dictionary and _dataTemplateIncrementer (starting at 2). The ViewTypeCount property returns 23 (ListViewAdapter.cs:125), so the effective limit is approximately 21 unique DataTemplate instances (incrementer starts at 2, and keys must stay below 23). When a new DataTemplate instance pushes the key beyond ViewTypeCount, Android's adapter contract is violated and the exception is thrown. The most common cause is a DataTemplateSelector that creates new DataTemplate instances per call to OnSelectTemplate instead of caching and reusing them.

Source

Thrown at src/Compatibility/Core.LegacyRenderers/Android/ListViewAdapter.cs:197

				}

				itemTemplate = selector.SelectTemplate(item, _listView);
			}

			// check again to guard against DataTemplateSelectors that return null
			if (itemTemplate == null)
				return DefaultItemTemplateId;

			if (!_templateToId.TryGetValue(itemTemplate, out int key))
			{
				_dataTemplateIncrementer++;
				key = _dataTemplateIncrementer;
				_templateToId[itemTemplate] = key;
			}

			if (key >= ViewTypeCount)
			{
				throw new Exception($"ItemTemplate count has exceeded the limit of {ViewTypeCount}" + Environment.NewLine +
									 "Please make sure to reuse DataTemplate objects");
			}

			return key;
		}

		public override AView GetView(int position, AView convertView, ViewGroup parent)
		{
			Cell cell = null;

			Performance.Start(out string reference);

			ListViewCachingStrategy cachingStrategy = Controller.CachingStrategy;
			var nextCellIsHeader = false;
			if (cachingStrategy == ListViewCachingStrategy.RetainElement || convertView == null)
			{
				if (_listView.IsGroupingEnabled)
				{

View on GitHub (pinned to f377ff1c5e)

Solutions

  1. Cache DataTemplate instances in the DataTemplateSelector as static or instance fields and return the cached references from OnSelectTemplate.
  2. If using a DataTemplateSelector, pre-create all templates once and reuse them: 'static readonly DataTemplate _templateA = new DataTemplate(typeof(ViewA));'
  3. Reduce the number of distinct templates to stay within the 20-template documented limit.
  4. If you truly need more view types, use a CollectionView with a custom handler or a third-party virtualized list that does not have the Android AdapterView ViewTypeCount constraint.
  5. Switch from ListView to CollectionView (Microsoft.Maui.Controls.CollectionView), which uses RecyclerView and has no hard ViewTypeCount limit.

Example fix

// before — creates new DataTemplate per call (causes the error)
public class MyTemplateSelector : DataTemplateSelector
{
    protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
    {
        var type = (item as MyModel)?.Type;
        return type switch
        {
            "A" => new DataTemplate(() => new ViewA()),
            "B" => new DataTemplate(() => new ViewB()),
            _ => new DataTemplate(() => new ViewDefault())
        };
    }
}
// after — cached and reused (fixes the error)
public class MyTemplateSelector : DataTemplateSelector
{
    readonly DataTemplate _a = new DataTemplate(() => new ViewA());
    readonly DataTemplate _b = new DataTemplate(() => new ViewB());
    readonly DataTemplate _default = new DataTemplate(() => new ViewDefault());

    protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
    {
        var type = (item as MyModel)?.Type;
        return type switch
        {
            "A" => _a,
            "B" => _b,
            _ => _default
        };
    }
}
Defensive patterns

Strategy: validation

Validate before calling

// Pre-validate: count unique DataTemplate instances your selector can return
var uniqueTemplates = new HashSet<DataTemplate>();
foreach (var item in sampleItems)
{
    var template = selector.SelectTemplate(item, listView);
    uniqueTemplates.Add(template);
}
if (uniqueTemplates.Count > 20)
{
    throw new InvalidOperationException($"DataTemplateSelector returns {uniqueTemplates.Count} unique DataTemplate instances; limit is ~20. Cache and reuse templates.");
}

Type guard

// Verify the DataTemplateSelector caches its templates (no per-call allocation)
static bool SelectorCachesTemplates(DataTemplateSelector selector)
{
    // Call SelectTemplate twice with equivalent items and check reference equality
    var dummy = new object();
    var t1 = selector.SelectTemplate(dummy, null);
    var t2 = selector.SelectTemplate(dummy, null);
    return ReferenceEquals(t1, t2);
}

Prevention

When it happens

Trigger: GetItemViewType assigns a new incrementing key for each unique DataTemplate reference. If a DataTemplateSelector's SelectTemplate method returns 'new DataTemplate(() => new View())' each time it is called for different data items, every distinct return creates a new dictionary entry, and the counter eventually exceeds 23. The exception fires on the item that pushes it over.

Common situations: DataTemplateSelector returning new DataTemplate instances in OnSelectTemplate without caching; creating DataTemplate objects inside a data-binding converter or inside a cell factory that runs per-item; binding a ListView's ItemTemplate to a property that returns a new DataTemplate each getter call; using a lambda-based DataTemplate constructor inside a hot path; large heterogeneous lists where each item type gets a distinct template and there are more than ~20 types.

Related errors


AI-assisted analysis of dotnet/maui@f377ff1c5e (2026-08-13). Data as JSON: /api/errors/360851da02e4b7a0. Report an issue: GitHub.