dotnet/maui · error · Exception

ItemTemplate count has exceeded the limit of

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.

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.

Appendix: 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)