[{"data":1,"prerenderedAt":23},["ShallowReactive",2],{"articolo-widget-riutilizzabili-in-flutter-comporre-ui-con-il-pattern-della-composizione":3,"comments-article-widget-riutilizzabili-in-flutter-comporre-ui-con-il-pattern-della-composizione":22},{"id":4,"title":5,"slug":6,"excerpt":7,"body":8,"cover_image":9,"video_url":10,"status":11,"published_at":12,"meta_title":13,"meta_description":14,"category":15,"author":19},35,"Widget riutilizzabili in Flutter: comporre UI con il pattern della composizione","widget-riutilizzabili-in-flutter-comporre-ui-con-il-pattern-della-composizione","Impara a costruire componenti Flutter riutilizzabili e manutenibili sfruttando la composizione al posto dell'ereditarietà, con esempi pratici di slot, builder e widget parametrizzati.","## Perché la composizione è la chiave in Flutter\n\nUno dei principi fondamentali di Flutter è \"composition over inheritance\": invece di estendere classi per aggiungere comportamenti, si combinano widget piccoli e focalizzati per costruirne di più complessi. Adottare questo approccio in modo consapevole porta a UI più leggibili, testabili e riutilizzabili.\n\nIn questo articolo vedremo tecniche concrete per progettare widget realmente riutilizzabili, evitando gli errori più comuni che rendono il codice rigido e difficile da mantenere.\n\n## Il problema dei widget monolitici\n\nCapita spesso di creare un widget che fa troppe cose: gestisce lo stato, definisce lo stile, contiene logica di business e magari anche chiamate di rete. Il risultato è un componente impossibile da riutilizzare in un contesto diverso.\n\nLa soluzione è scomporre il widget in parti più piccole, ognuna con una singola responsabilità, e ricomporle.\n\n```dart\n\u002F\u002F Widget poco riutilizzabile: stile e contenuto sono fusi insieme\nclass PromoCard extends StatelessWidget {\n  const PromoCard({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return Container(\n      padding: const EdgeInsets.all(16),\n      decoration: BoxDecoration(\n        color: Colors.blue.shade50,\n        borderRadius: BorderRadius.circular(12),\n      ),\n      child: const Text('Offerta speciale del giorno!'),\n    );\n  }\n}\n```\n\n## Parametrizzare con i widget come proprietà\n\nUn modo potente per rendere flessibile un componente è passare altri widget come parametri. Questo è esattamente ciò che fanno molti widget del framework (pensa a `Scaffold` con `appBar`, `body`, `floatingActionButton`).\n\n```dart\nclass AppCard extends StatelessWidget {\n  const AppCard({\n    super.key,\n    required this.child,\n    this.leading,\n    this.trailing,\n    this.onTap,\n  });\n\n  final Widget child;\n  final Widget? leading;\n  final Widget? trailing;\n  final VoidCallback? onTap;\n\n  @override\n  Widget build(BuildContext context) {\n    return Material(\n      color: Theme.of(context).colorScheme.surfaceContainer,\n      borderRadius: BorderRadius.circular(12),\n      child: InkWell(\n        onTap: onTap,\n        borderRadius: BorderRadius.circular(12),\n        child: Padding(\n          padding: const EdgeInsets.all(16),\n          child: Row(\n            children: [\n              if (leading != null) ...[leading!, const SizedBox(width: 12)],\n              Expanded(child: child),\n              if (trailing != null) ...[const SizedBox(width: 12), trailing!],\n            ],\n          ),\n        ),\n      ),\n    );\n  }\n}\n```\n\nQuesto `AppCard` definisce la struttura e lo stile, ma lascia al chiamante la libertà di decidere il contenuto. Gli slot opzionali (`leading`, `trailing`) rendono il componente adattabile a molti casi d'uso.\n\n## Il pattern builder per contenuti dinamici\n\nQuando il contenuto dipende da uno stato interno del widget, passare un widget statico non basta. In questi casi si usa il **builder pattern**: una funzione che riceve dati e restituisce un widget.\n\n```dart\nclass ExpandablePanel extends StatefulWidget {\n  const ExpandablePanel({\n    super.key,\n    required this.header,\n    required this.bodyBuilder,\n  });\n\n  final Widget header;\n  final Widget Function(BuildContext context, bool isExpanded) bodyBuilder;\n\n  @override\n  State\u003CExpandablePanel> createState() => _ExpandablePanelState();\n}\n\nclass _ExpandablePanelState extends State\u003CExpandablePanel> {\n  bool _expanded = false;\n\n  @override\n  Widget build(BuildContext context) {\n    return Column(\n      crossAxisAlignment: CrossAxisAlignment.stretch,\n      children: [\n        InkWell(\n          onTap: () => setState(() => _expanded = !_expanded),\n          child: widget.header,\n        ),\n        AnimatedCrossFade(\n          duration: const Duration(milliseconds: 250),\n          crossFadeState:\n              _expanded ? CrossFadeState.showSecond : CrossFadeState.showFirst,\n          firstChild: const SizedBox(width: double.infinity),\n          secondChild: widget.bodyBuilder(context, _expanded),\n        ),\n      ],\n    );\n  }\n}\n```\n\nIl builder consente di esporre uno stato interno (`isExpanded`) senza costringere il chiamante a gestirlo direttamente.\n\n## Estrarre logica con i mixin (quando ha senso)\n\nLa composizione non riguarda solo la UI. Per comportamenti trasversali come i controller di animazione, i mixin restano uno strumento valido:\n\n```dart\nclass PulseButton extends StatefulWidget {\n  const PulseButton({super.key, required this.child, this.onPressed});\n\n  final Widget child;\n  final VoidCallback? onPressed;\n\n  @override\n  State\u003CPulseButton> createState() => _PulseButtonState();\n}\n\nclass _PulseButtonState extends State\u003CPulseButton>\n    with SingleTickerProviderStateMixin {\n  late final AnimationController _controller = AnimationController(\n    vsync: this,\n    duration: const Duration(milliseconds: 120),\n    lowerBound: 0.95,\n    upperBound: 1.0,\n    value: 1.0,\n  );\n\n  @override\n  void dispose() {\n    _controller.dispose();\n    super.dispose();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return GestureDetector(\n      onTapDown: (_) => _controller.reverse(),\n      onTapUp: (_) {\n        _controller.forward();\n        widget.onPressed?.call();\n      },\n      onTapCancel: () => _controller.forward(),\n      child: ScaleTransition(scale: _controller, child: widget.child),\n    );\n  }\n}\n```\n\n## Best practice per widget riutilizzabili\n\n- **Una responsabilità per widget**: separa presentazione, layout e logica.\n- **Preferisci `const` costruttori**: migliorano le performance evitando ricostruzioni inutili.\n- **Esponi API minimali ma flessibili**: usa parametri opzionali e valori di default sensati.\n- **Evita dipendenze nascoste**: un widget riutilizzabile non dovrebbe conoscere lo stato globale dell'app; ricevi i dati tramite parametri o callback.\n- **Documenta gli slot**: chiarisci cosa ci si aspetta in `child`, `leading`, ecc.\n- **Non abusare dell'ereditarietà**: estendere `StatelessWidget` o `StatefulWidget` va bene, ma evita gerarchie profonde di widget custom.\n\n## Conclusione\n\nProgettare con la composizione richiede un piccolo cambio di mentalità, ma ripaga con codice più pulito, componenti facilmente testabili e una UI che cresce senza diventare ingestibile. Slot, builder e widget parametrizzati sono gli strumenti che Flutter stesso usa: sfruttarli nel proprio codice significa scrivere widget davvero riutilizzabili.","https:\u002F\u002Fflutter.it\u002Fstorage\u002Farticles\u002F24fd7871-1937-4adb-8547-a3b2ee4fc575.jpg",null,"published","2026-07-09T04:00:42+00:00","Widget riutilizzabili in Flutter con la composizione","Guida pratica per creare widget Flutter riutilizzabili e manutenibili con composizione, slot, builder pattern e best practice ed esempi in Dart.",{"id":16,"name":17,"slug":18},3,"Best practice","best-practice",{"id":20,"name":21},1,"Flutter Bot",[],1785219602510]