Il problema delle stringhe magiche
In quasi ogni progetto Flutter prima o poi compare una riga come questa:
Image.asset('assets/images/profile_placeholder.png')
Funziona, finché qualcuno non rinomina il file, non sposta la cartella o non sbaglia una lettera. Il compilatore non dice nulla: l'errore arriva a runtime, spesso in produzione, sotto forma di un rettangolo grigio con l'eccezione Unable to load asset.
Lo stesso vale per i font (fontFamily: 'Montserrat'), per i file JSON di configurazione, per le animazioni Lottie e per le icone SVG. Sono tutte dipendenze reali del codice, ma espresse in un formato che nessuno strumento può verificare.
flutter_gen risolve il problema alla radice: analizza il pubspec.yaml e la cartella degli asset e genera classi Dart tipizzate. Da quel momento Assets.images.profilePlaceholder è un simbolo vero, con autocompletamento, go to definition e refactoring automatico.
Installazione e configurazione
flutter_gen non è una dipendenza runtime: è un generatore. Si aggiunge quindi tra le dev_dependencies.
dev_dependencies:
build_runner: ^2.4.13
flutter_gen_runner: ^5.8.0
La configurazione vive direttamente nel pubspec.yaml, sotto la chiave di primo livello flutter_gen:
flutter:
uses-material-design: true
assets:
- assets/images/
- assets/icons/
- assets/lottie/
- assets/json/
fonts:
- family: Montserrat
fonts:
- asset: assets/fonts/Montserrat-Regular.ttf
- asset: assets/fonts/Montserrat-Bold.ttf
weight: 700
flutter_gen:
output: lib/gen/ # dove finiscono i file generati
line_length: 80
integrations:
flutter_svg: true # genera il metodo .svg()
lottie: true # genera il metodo .lottie()
rive: true
assets:
enabled: true
outputs:
class_name: Assets
style: dot-delimiter # Assets.images.logo
package_parameter_enabled: false
fonts:
enabled: true
colors:
inputs:
- assets/colors/colors.xml
Per generare il codice:
dart run build_runner build --delete-conflicting-outputs
In alternativa si può installare la CLI globale, utile quando non si vuole far girare l'intera pipeline di build_runner:
dart pub global activate flutter_gen
fluttergen -c pubspec.yaml
Il risultato sono file come lib/gen/assets.gen.dart, lib/gen/fonts.gen.dart e lib/gen/colors.gen.dart.
Usare gli asset generati
Immagini
Ogni immagine diventa un oggetto AssetGenImage con metodi pronti all'uso:
import 'package:myapp/gen/assets.gen.dart';
// Il path grezzo, se serve
final path = Assets.images.logo.path; // 'assets/images/logo.png'
// Widget Image già configurato
Assets.images.logo.image(
width: 120,
fit: BoxFit.contain,
filterQuality: FilterQuality.medium,
);
// ImageProvider, utile per DecorationImage o precaching
Container(
decoration: BoxDecoration(
image: DecorationImage(
image: Assets.images.background.provider(),
fit: BoxFit.cover,
),
),
);
Le varianti di risoluzione (2.0x/, 3.0x/) continuano a funzionare esattamente come prima: flutter_gen genera un solo simbolo per l'immagine base, mentre Flutter sceglie a runtime la variante corretta in base al devicePixelRatio.
SVG
Con flutter_svg: true nelle integrazioni, i file .svg espongono un metodo dedicato:
Assets.icons.menu.svg(
width: 24,
height: 24,
colorFilter: ColorFilter.mode(
Theme.of(context).colorScheme.onSurface,
BlendMode.srcIn,
),
);
Il pacchetto flutter_svg deve ovviamente essere tra le dipendenze del progetto: flutter_gen si limita a generare la chiamata.
Lottie, Rive e file generici
// Animazione Lottie
Assets.lottie.loading.lottie(repeat: true);
// File JSON o qualsiasi altro asset: resta una String tipizzata
final raw = await rootBundle.loadString(Assets.json.remoteConfigDefaults);
Font
import 'package:myapp/gen/fonts.gen.dart';
TextStyle(
fontFamily: FontFamily.montserrat,
fontWeight: FontWeight.w700,
);
Se un giorno il designer cambia font e il family nel pubspec diventa Inter, la rigenerazione fa esplodere a compile time tutti i punti che usavano FontFamily.montserrat. È esattamente quello che vogliamo.
Colori da XML
Se condividi la palette con il team Android (o la esporti da Figma in formato XML), flutter_gen può generarla:
<!-- assets/colors/colors.xml -->
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="primary">#FF6750A4</color>
<color name="surface_dim">#FFDED8E1</color>
</resources>
import 'package:myapp/gen/colors.gen.dart';
ColoredBox(color: ColorName.primary);
Nota: per un design system completo restano preferibili
ThemeExtensioneColorScheme;ColorNameè comodo come sorgente di valori grezzi da iniettare nel tema, non come sostituto del tema stesso.
Un caso d'uso concreto: precaching delle immagini
Una delle funzioni meno note è la lista values, che raccoglie tutti gli asset di una cartella. Diventa banale fare il precache delle immagini critiche dello splash o dell'onboarding:
Future<void> precacheOnboarding(BuildContext context) async {
await Future.wait([
for (final image in Assets.images.onboarding.values)
precacheImage(image.provider(), context),
]);
}
Se domani il designer aggiunge una quarta schermata di onboarding, il file viene incluso automaticamente: nessuna lista da aggiornare a mano.
Organizzare le cartelle per avere nomi leggibili
I nomi generati derivano direttamente dal filesystem, quindi la struttura delle cartelle è la tua API. Qualche regola pratica:
- usa
snake_caseper i file:profile_placeholder.pngdiventaprofilePlaceholder, mentreProfile Placeholder.pnggenera nomi poco gradevoli (gli spazi vengono normalizzati, ma meglio evitarli); - non iniziare i nomi con un numero:
1_step.pngnon è un identificatore Dart valido e viene prefissato automaticamente; - raggruppa per dominio, non per tipo:
assets/images/onboarding/,assets/images/empty_states/sono più utili di una cartellaimagespiatta con 80 file; - attenzione ai duplicati:
logo.pngelogo.svgnella stessa cartella generano collisioni di nome. Rinomina uno dei due (logo_mark.svg).
Se preferisci nomi "piatti" puoi cambiare lo style in snake-case o camel-case: otterrai Assets.imagesOnboardingStepOne invece di Assets.images.onboarding.stepOne. Nella maggior parte dei casi dot-delimiter è più leggibile.
Monorepo e package condivisi
Se gli asset vivono in un package separato (scenario comune con Melos), attiva:
flutter_gen:
assets:
outputs:
package_parameter_enabled: true
Il codice generato includerà automaticamente il parametro package:, evitando il classico errore "asset non trovato" quando si carica una risorsa da un package diverso da quello dell'app host.
Generated code e controllo di versione
La domanda ricorrente: committare o no i file in lib/gen/?
Committarli è la scelta più pragmatica per i team piccoli: l'IDE funziona subito dopo il clone, le PR mostrano esplicitamente quando un asset viene aggiunto o rimosso e la CI non deve eseguire un passo extra prima dell'analisi statica.
Se invece preferisci tenerli fuori dal repository, aggiungi a .gitignore la cartella di output e inserisci la generazione nella pipeline prima di flutter analyze:
- run: flutter pub get
- run: dart run build_runner build --delete-conflicting-outputs
- run: flutter analyze
- run: flutter test
In entrambi i casi conviene escludere il codice generato dall'analisi statica e dal coverage:
# analysis_options.yaml
analyzer:
exclude:
- lib/gen/**
- "**/*.g.dart"
Cosa flutter_gen non fa
È importante conoscere i limiti dello strumento:
- non ottimizza gli asset: comprimere i PNG, convertire in WebP o ripulire gli SVG resta compito tuo (o di un passo di build dedicato);
- non verifica a runtime: se cancelli un file e dimentichi di rigenerare, il codice compila ancora e fallisce a runtime. Per questo vale la pena aggiungere un check in CI che rigeneri e confronti il diff (
git diff --exit-code lib/gen/); - non sostituisce la localizzazione: per le stringhe servono
intloslang; - non riduce il peso del bundle: se un asset è dichiarato nel pubspec viene incluso, anche se nessuno lo referenzia. Un vantaggio indiretto però c'è: con i simboli generati è molto più facile cercare le referenze e capire quali immagini sono davvero inutilizzate prima di cancellarle.
Workflow consigliato
- Aggiungi l'asset nella cartella corretta con un nome in
snake_case. - Verifica che la cartella sia dichiarata in
flutter: assets:(basta la directory, non il singolo file). - Rigenera con
dart run build_runner build --delete-conflicting-outputs— oppure tieni attivowatchdurante lo sviluppo:
dart run build_runner watch --delete-conflicting-outputs
- Usa il simbolo generato e vieta per convenzione (anche via code review) qualsiasi
Image.asset('...')con stringa letterale.
Per rendere la regola effettiva puoi persino aggiungere un lint custom con custom_lint che segnala l'uso di Image.asset con argomento letterale, ma nella pratica è sufficiente che il team adotti l'abitudine: una volta provato l'autocompletamento su Assets., tornare indietro è difficile.
Conclusione
flutter_gen è uno di quegli strumenti che costano cinque minuti di setup e restituiscono valore per tutta la vita del progetto. Trasforma risorse fragili — path, nomi di font, palette — in simboli Dart verificati dal compilatore, elimina un'intera categoria di bug a runtime e rende i refactoring degli asset un'operazione meccanica invece che un esercizio di archeologia tra le stringhe.