[{"data":1,"prerenderedAt":74},["ShallowReactive",2],{"tutorial-widget-riutilizzabili-e-componibili-in-flutter-crea-la-tua-ui-library":3,"comments-tutorial-widget-riutilizzabili-e-componibili-in-flutter-crea-la-tua-ui-library":73},{"id":4,"title":5,"slug":6,"excerpt":7,"intro":8,"cover_image":9,"video_url":10,"difficulty":11,"estimated_minutes":12,"flutter_version":13,"status":14,"published_at":15,"meta_title":16,"meta_description":17,"category":18,"author":22,"steps":25},45,"Widget riutilizzabili e componibili in Flutter: crea la tua UI library","widget-riutilizzabili-e-componibili-in-flutter-crea-la-tua-ui-library","Impara a costruire widget personalizzati, riutilizzabili e componibili in Flutter per mantenere il codice pulito, coerente e facile da mantenere.","## Perché creare widget riutilizzabili\n\nUno degli errori più comuni tra chi inizia con Flutter è duplicare lo stesso codice UI in decine di schermate. Questo rende l'app difficile da mantenere e le modifiche allo stile diventano un incubo.\n\nIn questo tutorial impareremo a estrarre i widget, a costruire componenti configurabili tramite parametri, a usare `child` e `builder` per la composizione, e a creare una piccola **UI library** interna coerente. Applicheremo alcune best practice come l'uso di `const`, la separazione dei componenti e la creazione di widget \"presentazionali\" indipendenti dalla logica di business.\n\nAlla fine avrai un pulsante primario, una card e un widget di stato vuoto pronti da riutilizzare in tutta l'app.","https:\u002F\u002Fflutter.it\u002Fstorage\u002Ftutorials\u002F0797e2da-7ec0-4b54-9157-de0ea3b30120.jpg",null,"intermediate",35,"3.x","published","2026-07-22T04:30:58+00:00","Widget riutilizzabili in Flutter: UI library componibile","Guida pratica per creare widget Flutter riutilizzabili e componibili: pulsanti, card e componenti configurabili per una UI coerente e manutenibile.",{"id":19,"name":20,"slug":21},3,"Best practice","best-practice",{"id":23,"name":24},1,"Flutter Bot",[26,33,40,46,53,60,67],{"id":27,"position":23,"title":28,"body":29,"code_snippet":30,"code_language":31,"expected_result":32,"demo_url":10,"video_url":10},303,"Estrarre un widget invece di duplicare il codice","Il primo passo verso la riutilizzabilità è **estrarre** in una classe separata un blocco di UI che si ripete. Preferisci sempre creare un `StatelessWidget` (o `StatefulWidget`) rispetto a un metodo che ritorna un `Widget`: la classe è ottimizzata dal framework, supporta i `const` e viene ricostruita in modo più efficiente.\n\nCreiamo il file `lib\u002Fui\u002Fprimary_button.dart` con un pulsante primario configurabile.","import 'package:flutter\u002Fmaterial.dart';\n\nclass PrimaryButton extends StatelessWidget {\n  const PrimaryButton({\n    super.key,\n    required this.label,\n    required this.onPressed,\n    this.isLoading = false,\n    this.icon,\n  });\n\n  final String label;\n  final VoidCallback? onPressed;\n  final bool isLoading;\n  final IconData? icon;\n\n  @override\n  Widget build(BuildContext context) {\n    return FilledButton(\n      onPressed: isLoading ? null : onPressed,\n      child: isLoading\n          ? const SizedBox(\n              height: 20,\n              width: 20,\n              child: CircularProgressIndicator(strokeWidth: 2),\n            )\n          : Row(\n              mainAxisSize: MainAxisSize.min,\n              children: [\n                if (icon != null) ...[\n                  Icon(icon, size: 18),\n                  const SizedBox(width: 8),\n                ],\n                Text(label),\n              ],\n            ),\n    );\n  }\n}","dart","Hai un widget PrimaryButton che gestisce label, icona opzionale e stato di caricamento, riutilizzabile ovunque.",{"id":34,"position":35,"title":36,"body":37,"code_snippet":38,"code_language":31,"expected_result":39,"demo_url":10,"video_url":10},304,2,"Rendere il widget configurabile con parametri e valori di default","Un buon widget riutilizzabile espone **parametri chiari** con valori di default sensati. Evita di codificare colori o spaziature fisse: leggi i valori dal `Theme` così il componente si adatta automaticamente al tema chiaro\u002Fscuro.\n\nCreiamo una `AppCard` che accetta un `child`, un padding personalizzabile e un callback opzionale al tap.","import 'package:flutter\u002Fmaterial.dart';\n\nclass AppCard extends StatelessWidget {\n  const AppCard({\n    super.key,\n    required this.child,\n    this.padding = const EdgeInsets.all(16),\n    this.onTap,\n  });\n\n  final Widget child;\n  final EdgeInsetsGeometry padding;\n  final VoidCallback? onTap;\n\n  @override\n  Widget build(BuildContext context) {\n    final colors = Theme.of(context).colorScheme;\n    return Material(\n      color: colors.surfaceContainerHighest,\n      borderRadius: BorderRadius.circular(16),\n      clipBehavior: Clip.antiAlias,\n      child: InkWell(\n        onTap: onTap,\n        child: Padding(\n          padding: padding,\n          child: child,\n        ),\n      ),\n    );\n  }\n}","AppCard rispetta il tema dell'app, è cliccabile solo se onTap è fornito e accetta qualsiasi contenuto tramite child.",{"id":41,"position":19,"title":42,"body":43,"code_snippet":44,"code_language":31,"expected_result":45,"demo_url":10,"video_url":10},305,"Comporre i widget con il pattern child e builder","La **composizione** è il cuore di Flutter: invece di ereditare, si combinano widget piccoli e specializzati. Usare una proprietà `child` (o `builder`) mantiene i componenti generici e flessibili.\n\nCreiamo un `SectionTile` che combina `AppCard` con un titolo e uno slot per il contenuto. Nota come riutilizziamo `AppCard` senza duplicarne la logica.","import 'package:flutter\u002Fmaterial.dart';\nimport 'app_card.dart';\n\nclass SectionTile extends StatelessWidget {\n  const SectionTile({\n    super.key,\n    required this.title,\n    required this.child,\n    this.trailing,\n  });\n\n  final String title;\n  final Widget child;\n  final Widget? trailing;\n\n  @override\n  Widget build(BuildContext context) {\n    final textTheme = Theme.of(context).textTheme;\n    return AppCard(\n      child: Column(\n        crossAxisAlignment: CrossAxisAlignment.start,\n        children: [\n          Row(\n            children: [\n              Expanded(\n                child: Text(title, style: textTheme.titleMedium),\n              ),\n              if (trailing != null) trailing!,\n            ],\n          ),\n          const SizedBox(height: 12),\n          child,\n        ],\n      ),\n    );\n  }\n}","SectionTile mostra un titolo con un'azione opzionale e il contenuto sottostante, riutilizzando AppCard.",{"id":47,"position":48,"title":49,"body":50,"code_snippet":51,"code_language":31,"expected_result":52,"demo_url":10,"video_url":10},306,4,"Creare un widget di stato vuoto riutilizzabile","Gli stati vuoti (liste senza dati, ricerche senza risultati) si ripetono spesso. Un componente dedicato garantisce coerenza visiva in tutta l'app.\n\nCreiamo `EmptyState` con icona, titolo, messaggio e un'azione opzionale che riusa il nostro `PrimaryButton`.","import 'package:flutter\u002Fmaterial.dart';\nimport 'primary_button.dart';\n\nclass EmptyState extends StatelessWidget {\n  const EmptyState({\n    super.key,\n    required this.icon,\n    required this.title,\n    this.message,\n    this.actionLabel,\n    this.onAction,\n  });\n\n  final IconData icon;\n  final String title;\n  final String? message;\n  final String? actionLabel;\n  final VoidCallback? onAction;\n\n  @override\n  Widget build(BuildContext context) {\n    final theme = Theme.of(context);\n    return Center(\n      child: Padding(\n        padding: const EdgeInsets.all(24),\n        child: Column(\n          mainAxisSize: MainAxisSize.min,\n          children: [\n            Icon(icon, size: 64, color: theme.colorScheme.outline),\n            const SizedBox(height: 16),\n            Text(title, style: theme.textTheme.titleLarge, textAlign: TextAlign.center),\n            if (message != null) ...[\n              const SizedBox(height: 8),\n              Text(\n                message!,\n                style: theme.textTheme.bodyMedium,\n                textAlign: TextAlign.center,\n              ),\n            ],\n            if (actionLabel != null && onAction != null) ...[\n              const SizedBox(height: 24),\n              PrimaryButton(label: actionLabel!, onPressed: onAction),\n            ],\n          ],\n        ),\n      ),\n    );\n  }\n}","Un componente EmptyState pronto da mostrare quando una lista è vuota, con azione facoltativa.",{"id":54,"position":55,"title":56,"body":57,"code_snippet":58,"code_language":31,"expected_result":59,"demo_url":10,"video_url":10},307,5,"Centralizzare gli export in un barrel file","Con più componenti conviene creare un **barrel file** che raccoglie tutti gli export della tua UI library. Così le import nelle schermate restano pulite: basta un solo `import`.\n\nCrea `lib\u002Fui\u002Fui.dart`.","\u002F\u002F lib\u002Fui\u002Fui.dart\nexport 'primary_button.dart';\nexport 'app_card.dart';\nexport 'section_tile.dart';\nexport 'empty_state.dart';","Puoi importare tutti i componenti con `import 'package:tua_app\u002Fui\u002Fui.dart';`.",{"id":61,"position":62,"title":63,"body":64,"code_snippet":65,"code_language":31,"expected_result":66,"demo_url":10,"video_url":10},308,6,"Usare i componenti in una schermata reale","Mettiamo insieme tutto in una schermata di esempio. Nota come il codice della UI resta **leggibile e dichiarativo**: nessuna duplicazione di stile, tutto passa dai componenti riutilizzabili.","import 'package:flutter\u002Fmaterial.dart';\nimport 'package:tua_app\u002Fui\u002Fui.dart';\n\nclass DashboardScreen extends StatelessWidget {\n  const DashboardScreen({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    final items = \u003CString>[]; \u002F\u002F simuliamo una lista vuota\n\n    return Scaffold(\n      appBar: AppBar(title: const Text('Dashboard')),\n      body: items.isEmpty\n          ? EmptyState(\n              icon: Icons.inbox_outlined,\n              title: 'Nessun elemento',\n              message: 'Aggiungi il tuo primo elemento per iniziare.',\n              actionLabel: 'Aggiungi',\n              onAction: () {},\n            )\n          : ListView(\n              padding: const EdgeInsets.all(16),\n              children: [\n                SectionTile(\n                  title: 'Riepilogo',\n                  trailing: const Icon(Icons.more_horiz),\n                  child: const Text('Contenuto della sezione'),\n                ),\n                const SizedBox(height: 12),\n                PrimaryButton(\n                  label: 'Continua',\n                  icon: Icons.arrow_forward,\n                  onPressed: () {},\n                ),\n              ],\n            ),\n    );\n  }\n}","La schermata mostra lo stato vuoto con un pulsante 'Aggiungi' coerente con il resto dell'app, dimostrando i componenti in azione.",{"id":68,"position":69,"title":70,"body":71,"code_snippet":10,"code_language":31,"expected_result":72,"demo_url":10,"video_url":10},309,7,"Best practice finali per una UI library manutenibile","Alcune regole pratiche per far crescere la tua libreria di widget senza creare caos:\n\n- **Usa sempre `const`** dove possibile per evitare ricostruzioni inutili.\n- **Mantieni i widget presentazionali**: nessuna chiamata di rete o logica di business al loro interno. Passa i dati e i callback dall'esterno.\n- **Non hardcodare colori e dimensioni**: leggi da `Theme.of(context)` per supportare dark mode e coerenza.\n- **Espone parametri opzionali con default sensati** invece di creare tante varianti di widget.\n- **Documenta i parametri** con commenti `\u002F\u002F\u002F` così l'IDE mostra suggerimenti utili.\n- **Testa i widget** con `flutter_test` per garantire che le modifiche future non rompano la UI.\n\nSeguendo questi principi, aggiungere nuove schermate diventa un lavoro di assemblaggio di mattoncini già collaudati.","Hai una UI library iniziale, coerente e riutilizzabile, e conosci le best practice per estenderla in sicurezza.",[],1785219598298]