Cosa sono le estensioni in Dart
Gli extension methods sono una funzionalità di Dart (disponibile dalla versione 2.7) che consente di aggiungere metodi, getter, setter e operatori a classi già esistenti, anche a quelle che non possiamo modificare come String, int, List o le classi del framework Flutter come BuildContext.
Sono particolarmente utili in Flutter per ridurre il codice ripetitivo (boilerplate) e migliorare la leggibilità, mantenendo una sintassi fluente e naturale.
Sintassi di base
Una estensione si dichiara con la parola chiave extension, seguita opzionalmente da un nome, dal tipo che si vuole estendere e dal corpo:
extension StringExtensions on String {
bool get isValidEmail {
final regex = RegExp(r'^[\w.-]+@[\w-]+\.[\w.-]+\$');
return regex.hasMatch(this);
}
String capitalize() {
if (isEmpty) return this;
return '\${this[0].toUpperCase()}\${substring(1)}';
}
}
Una volta importata l'estensione, i metodi si utilizzano come se fossero nativi del tipo:
final email = 'utente@esempio.it';
print(email.isValidEmail); // true
print('flutter'.capitalize()); // Flutter
Dentro l'estensione, this fa riferimento all'istanza su cui il metodo viene chiamato.
Estendere BuildContext: il caso d'uso più comune
Uno degli usi più diffusi in Flutter è estendere BuildContext per accorciare l'accesso a Theme, MediaQuery e alla navigazione:
extension ContextExtensions on BuildContext {
ThemeData get theme => Theme.of(this);
TextTheme get textTheme => Theme.of(this).textTheme;
ColorScheme get colors => Theme.of(this).colorScheme;
Size get screenSize => MediaQuery.sizeOf(this);
double get screenWidth => MediaQuery.sizeOf(this).width;
double get screenHeight => MediaQuery.sizeOf(this).height;
bool get isDarkMode => Theme.of(this).brightness == Brightness.dark;
}
Il risultato è un codice molto più conciso all'interno dei widget:
@override
Widget build(BuildContext context) {
return Text(
'Ciao Flutter',
style: context.textTheme.headlineMedium?.copyWith(
color: context.colors.primary,
),
);
}
Estensioni per liste e widget
Un pattern utile è aggiungere spaziatura tra i figli di una Column o Row:
extension WidgetListExtensions on List<Widget> {
List<Widget> separatedBy(Widget separator) {
if (isEmpty) return this;
return [
for (var i = 0; i < length; i++) ...[
this[i],
if (i < length - 1) separator,
],
];
}
}
Utilizzo:
Column(
children: [
Text('Primo'),
Text('Secondo'),
Text('Terzo'),
].separatedBy(const SizedBox(height: 12)),
)
Possiamo anche estendere direttamente i widget per applicare modificatori in stile fluente:
extension PaddingExtension on Widget {
Widget paddingAll(double value) =>
Padding(padding: EdgeInsets.all(value), child: this);
Widget get expanded => Expanded(child: this);
Widget center() => Center(child: this);
}
Text('In evidenza').paddingAll(16).center();
Estensioni generiche e con vincoli
Le estensioni supportano i generics e i vincoli di tipo, utili per operare su collezioni tipizzate:
extension IterableExtensions<T> on Iterable<T> {
T? get firstOrNull => isEmpty ? null : first;
Iterable<T> separated(T separator) sync* {
final iterator = this.iterator;
if (!iterator.moveNext()) return;
yield iterator.current;
while (iterator.moveNext()) {
yield separator;
yield iterator.current;
}
}
}
Extension types: la novità di Dart 3
Con Dart 3 sono arrivati gli extension types, che permettono di creare un wrapper a costo zero attorno a un tipo esistente, offrendo type safety senza overhead a runtime:
extension type UserId(int value) {
bool get isValid => value > 0;
}
void fetchUser(UserId id) {
// id è distinto da un normale int a compile time
}
A differenza delle estensioni classiche, un extension type crea un nuovo tipo statico: questo evita ad esempio di confondere un UserId con un ProductId pur essendo entrambi int.
Best practice e limiti
- Un solo import esplicito: le estensioni devono essere importate per essere visibili; se due estensioni definiscono lo stesso metodo, si genera un conflitto risolvibile con
ExtensionName(oggetto).metodo(). - Non sovrascrivono i membri esistenti: se un tipo ha già un metodo con lo stesso nome, quello nativo ha la precedenza.
- Non possono aggiungere campi di istanza: solo metodi, getter, setter e operatori.
- Attenzione all'abuso: raggruppa le estensioni in file dedicati (es.
context_extensions.dart) e non nasconderci logica di business complessa.
Conclusione
Gli extension methods sono uno strumento semplice ma potentissimo per rendere il codice Flutter più espressivo e ridurre la ripetizione. Usati con moderazione e ben organizzati, migliorano notevolmente la leggibilità di un progetto. Con l'arrivo degli extension types di Dart 3, il pattern si arricchisce anche di garanzie di type safety a costo zero.