skip to content

In Flutter, how do you ship custom brand icons as an icon font, and why must each IconData for that font be const?

level: middleimportance: should knowfreq 35%

answer

  1. SVGs merged into one font
  2. a fonts: family in pubspec
  3. static const fields in a class
  4. the release build scans constants
  5. IconData became final in 3.44

basics

~20 s

Generate one font from the icon SVGs, declare it as a family in pubspec.yaml, and expose each glyph as a static const IconData. Release builds subset icon fonts by finding const IconData values, so a non-const one stops the build.

solid answer

~50 s

Convert the brand SVGs into a single `.ttf` with an icon-font generator, declare it under `flutter: fonts:` as a family such as `ClinicIcons`, and expose each glyph as `static const IconData(0xe801, fontFamily: 'ClinicIcons')` in a class annotated `@staticIconProvider`; add `fontPackage:` if the font lives in a package and `matchTextDirection: true` for directional glyphs. Const matters because release builds run the **icon tree shaker** by default: it finds every constant `IconData` and keeps only those glyphs. A non-const `IconData`, such as one built from a server-supplied integer, makes the build exit with 'This application cannot tree shake icons fonts' and a pointer to `--no-tree-shake-icons`. Since Flutter 3.44 `IconData` is a `final class` and its `codePoint`, `fontFamily` and `fontPackage` parameters are `@mustBeConst`, so the analyzer flags non-const arguments and the old `enum … implements IconData` pattern no longer compiles.

code

dart · 17 lines
dart
import 'package:flutter/widgets.dart';

@staticIconProvider
abstract final class ClinicIcons {
  static const String _family = 'ClinicIcons';

  static const IconData pawHeart = IconData(0xe800, fontFamily: _family);
  static const IconData syringe = IconData(0xe801, fontFamily: _family);
  static const IconData carrier = IconData(0xe802, fontFamily: _family);
  static const IconData backArrow = IconData(0xe803, fontFamily: _family, matchTextDirection: true);
}

IconData iconForVisit(String type) => switch (type) {
  'vaccination' => ClinicIcons.syringe,
  'transport' => ClinicIcons.carrier,
  _ => ClinicIcons.pawHeart,
};

go deeper

for a junior

Know the steps: one font file, a pubspec family, and static const IconData fields that name that family.

for a middle

Explain why const matters to the release build's glyph scan, what the build error says, and what fontPackage and matchTextDirection do.

for a senior

Design server-driven icons as mappings to constants, and migrate pre-3.44 IconData enums without losing type safety.

for a principal

Decide when a brand icon set belongs in an icon font versus vector assets, and who owns code-point stability as the set grows.

## Why a custom icon font A pet-clinic app has brand icons that no stock set contains: a paw with a heart, a syringe with a drop, a carrier crate. They are single-colour line drawings, which is exactly what an icon font is good at: every glyph scales cleanly, takes its colour from `IconTheme`, and the whole set costs one file. (Multi-colour marks belong in SVG instead.) ## Building and declaring the font 1. Export each icon as an SVG on the same canvas size. 2. Merge them into one `.ttf` with an icon-font generator, which assigns each glyph a code point in the Unicode private-use area and usually emits a code-point map. 3. Declare the font like any custom family: ```yaml flutter: fonts: - family: ClinicIcons fonts: - asset: fonts/ClinicIcons.ttf ``` 4. Expose each glyph as a constant: ```dart @staticIconProvider abstract final class ClinicIcons { static const String _family = 'ClinicIcons'; static const IconData pawHeart = IconData(0xe800, fontFamily: _family); static const IconData syringe = IconData(0xe801, fontFamily: _family); } ``` - **`fontFamily`** must equal the pubspec `family`. - **`fontPackage`** is required when the font is declared in a package, so `Icon` can resolve the namespaced family. - **`matchTextDirection: true`** makes `Icon` mirror a directional glyph in right-to-left locales. - **`@staticIconProvider`** marks a class that holds only `static const` icons, so the tree shaker can treat the declarations themselves as not being uses. ## Why every IconData must be const Release builds of Android, iOS, web and the other targets run the **icon tree shaker** by default (`--tree-shake-icons` is on; debug builds ignore it). The `flutter` tool asks a constant finder for every `IconData` instance in the compiled program, reads their code points, and subsets each icon font to those glyphs. The mechanism only works if every use is **known at compile time**: - A `const IconData(...)` is visible to the finder, with its code point. - `IconData(json['icon'] as int, fontFamily: 'ClinicIcons')` is not. The tool cannot know which glyphs it may need, so it stops with `This application cannot tree shake icons fonts. It has non-constant instances of IconData at the following locations:` and suggests building again with `--no-tree-shake-icons`. The fix is almost always to map dynamic keys to constants instead of constructing code points: ```dart IconData iconForVisit(String type) => switch (type) { 'vaccination' => ClinicIcons.syringe, _ => ClinicIcons.pawHeart, }; ``` Opting out with `--no-tree-shake-icons` works but ships every glyph of every icon font; how much that costs is an app-size question. ## Keeping code points stable The code point is the contract between the font file and the Dart constants. If a designer adds an icon and the generator renumbers the glyphs, `ClinicIcons.syringe` may silently draw the carrier crate instead — no compile error, no runtime error. Protect the mapping: - Commit the generator's code-point map next to the font and review changes to it like code. - Generate the Dart class from that map rather than editing constants by hand. - Append new glyphs at new code points; never reuse a retired one. - Keep a screenshot or golden check of an icon sheet so a swapped glyph is visible in review. ## What changed in Flutter 3.44 | Before 3.44 | Since 3.44 | |---|---| | `IconData` could be extended or implemented | `IconData` is a `final class` | | `enum AppIcons implements IconData` was a common pattern | that enum no longer compiles | | non-const arguments compiled silently | `codePoint`, `fontFamily` and `fontPackage` are `@mustBeConst`; the analyzer reports `non_const_argument_for_const_parameter` | The migration guide replaces the enum with a wrapper class holding `static const` instances and a small widget that renders `Icon(icon.iconData)`, keeping dot shorthands where the type can be inferred. If a truly dynamic icon is unavoidable, add `// ignore: non_const_argument_for_const_parameter` and accept that the build needs `--no-tree-shake-icons`. ## Checklist 1. One font per icon set, declared once. 2. Every glyph a `static const IconData` in an `@staticIconProvider` class. 3. Server- or database-driven icons mapped to constants, never built from integers. 4. Directional glyphs marked with `matchTextDirection: true`.

  • The server sends an icon code point per clinic service. How do you render it without breaking the release build?
    Do not build `IconData` from the integer. Map a server key to one of your `static const IconData` fields with a `switch` or a const map, with a fallback icon for unknown keys. The tree shaker then sees every glyph you can show. Constructing `IconData(code)` at runtime makes the release build exit unless you pass `--no-tree-shake-icons`.
  • Your team's enum AppIcons implements IconData stopped compiling on Flutter 3.44. What replaces it?
    `IconData` became a `final class`, so nothing outside its library can implement or extend it. Replace the enum with a class of `static const` wrapper instances that each hold a `const IconData`, plus a small widget that renders `Icon(icon.iconData)`. Keep a manual `values` list if tooling relied on the enum's.

saying these in an interview costs you the question

  • A non-const IconData crashes the app at runtime on release builds.
  • Custom icon fonts need no pubspec entry because Icon finds them automatically.
  • Enums implementing IconData are still the recommended pattern in Flutter 3.47.
  • fontPackage can be omitted even when the icon font lives in a package.
  • Icon tree shaking also runs in debug builds.