[{"data":1,"prerenderedAt":85},["ShallowReactive",2],{"tutorial-menu-a-tendina-in-flutter-dropdownbutton-dropdownbuttonformfield-e-dropdownmenu":3,"comments-tutorial-menu-a-tendina-in-flutter-dropdownbutton-dropdownbuttonformfield-e-dropdownmenu":84},{"id":4,"title":5,"slug":6,"excerpt":7,"intro":8,"cover_image":9,"cover_remote_url":10,"cover_credit":11,"video_url":15,"video":16,"difficulty":20,"estimated_minutes":21,"flutter_version":22,"status":23,"published_at":24,"meta_title":25,"meta_description":26,"category":27,"author":31,"steps":33},117,"Menu a tendina in Flutter: DropdownButton, DropdownButtonFormField e DropdownMenu","menu-a-tendina-in-flutter-dropdownbutton-dropdownbuttonformfield-e-dropdownmenu","Impara a creare menu a tendina in Flutter: dal classico DropdownButton alla validazione nei form, fino al moderno DropdownMenu di Material 3 e ai dropdown collegati tra loro.","Quasi ogni app ha bisogno di far scegliere all'utente una voce da un elenco: la lingua, la categoria di un prodotto, la città di residenza.\n\nIn Flutter esistono tre strumenti principali per farlo:\n\n- **`DropdownButton`**: il menu a tendina classico, semplice e immediato;\n- **`DropdownButtonFormField`**: la versione integrata nei `Form`, con validazione e decorazione;\n- **`DropdownMenu`**: il widget introdotto con Material 3, con campo di testo, filtro e ricerca.\n\nIn questo tutorial li vedremo tutti e tre partendo da zero, con esempi completi e funzionanti. Alla fine costruiremo anche due dropdown **collegati** (regione → provincia), un pattern molto comune nei form reali.\n\n> Prerequisiti: un progetto Flutter già creato e le basi di `StatefulWidget` e `setState`.","https:\u002F\u002Fflutter.it\u002Fstorage\u002Ftutorials\u002F524af2e6-0deb-4c3f-a9c8-8faf104018e8.jpg","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1762340916350-ad5a3d620c16?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzA2NTJ8MHwxfHJhbmRvbXx8fHx8fHx8fDE3OTEzNDc0ODN8&ixlib=rb-4.1.0&q=80&w=1080",{"name":12,"author_url":13,"photo_url":14},"Zulfugar Karimov","https:\u002F\u002Funsplash.com\u002F@zulfugarkarimov","https:\u002F\u002Funsplash.com\u002Fphotos\u002Fsecurity-privacy-and-performance-status-with-fix-options-7Og0reGku4M","https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=l4Rfets61uI",{"youtube_id":17,"duration_seconds":18,"published_at":19},"l4Rfets61uI",142,"2026-10-07T11:28:12+00:00","beginner",35,"3.x","published","2026-10-07T04:31:23+00:00","Menu a tendina in Flutter con DropdownButton","Guida pratica ai menu a tendina in Flutter: DropdownButton, DropdownButtonFormField con validazione e DropdownMenu di Material 3, con esempi completi.",{"id":28,"name":29,"slug":30},1,"Guide","guide",{"id":28,"name":32},"Flutter Bot",[34,42,49,56,63,70,77],{"id":35,"position":28,"title":36,"body":37,"code_snippet":38,"code_language":39,"expected_result":40,"demo_url":41,"video_url":41},783,"Preparare il progetto e i dati da mostrare","Creiamo un nuovo progetto (o apriamone uno esistente) e prepariamo una schermata vuota con un `Scaffold`.\n\nUn menu a tendina mostra una **lista di opzioni**: per tenere il codice ordinato conviene definirla come costante, separata dalla UI. In questo primo esempio usiamo una semplice lista di stringhe.\n\nAssicurati di avere `useMaterial3: true` nel tema: è il default nei progetti Flutter 3.x recenti e serve per l'esempio con `DropdownMenu` che vedremo più avanti.","import 'package:flutter\u002Fmaterial.dart';\n\nvoid main() => runApp(const MyApp());\n\nclass MyApp extends StatelessWidget {\n  const MyApp({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp(\n      title: 'Dropdown Demo',\n      theme: ThemeData(\n        colorSchemeSeed: Colors.indigo,\n        useMaterial3: true,\n      ),\n      home: const HomePage(),\n    );\n  }\n}\n\n\u002F\u002F\u002F Le opzioni che mostreremo nel menu a tendina.\nconst List\u003CString> kLinguaggi = \u003CString>[\n  'Dart',\n  'Kotlin',\n  'Swift',\n  'JavaScript',\n  'Python',\n];\n\nclass HomePage extends StatelessWidget {\n  const HomePage({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(title: const Text('Menu a tendina')),\n      body: const Padding(\n        padding: EdgeInsets.all(16),\n        child: Center(child: Text('Qui metteremo i dropdown')),\n      ),\n    );\n  }\n}","dart","L'app si avvia e mostra una schermata vuota con la AppBar \"Menu a tendina\".",null,{"id":43,"position":44,"title":45,"body":46,"code_snippet":47,"code_language":39,"expected_result":48,"demo_url":41,"video_url":41},784,2,"Il primo DropdownButton con stato","`DropdownButton` è un widget **controllato**: non memorizza da solo la scelta dell'utente. Tu gli passi il valore corrente tramite `value` e lui ti notifica i cambiamenti con `onChanged`. Per questo deve vivere dentro uno `StatefulWidget`.\n\nTre proprietà fondamentali:\n\n- **`items`**: la lista di `DropdownMenuItem`, ognuno con un `value` e un `child` (il widget mostrato);\n- **`value`**: il valore selezionato; può essere `null` (allora viene mostrato l'`hint`);\n- **`onChanged`**: callback invocata alla selezione. Se la imposti a `null`, il dropdown appare **disabilitato**.\n\n⚠️ Regola d'oro: il `value` deve essere `null` **oppure** corrispondere esattamente a uno dei `value` presenti in `items`, altrimenti Flutter lancia un'asserzione in fase di build.","class LinguaggioPicker extends StatefulWidget {\n  const LinguaggioPicker({super.key});\n\n  @override\n  State\u003CLinguaggioPicker> createState() => _LinguaggioPickerState();\n}\n\nclass _LinguaggioPickerState extends State\u003CLinguaggioPicker> {\n  String? _selezionato; \u002F\u002F all'inizio nessuna scelta\n\n  @override\n  Widget build(BuildContext context) {\n    return Column(\n      crossAxisAlignment: CrossAxisAlignment.start,\n      children: [\n        DropdownButton\u003CString>(\n          value: _selezionato,\n          hint: const Text('Scegli un linguaggio'),\n          items: kLinguaggi.map((String linguaggio) {\n            return DropdownMenuItem\u003CString>(\n              value: linguaggio,\n              child: Text(linguaggio),\n            );\n          }).toList(),\n          onChanged: (String? nuovo) {\n            setState(() => _selezionato = nuovo);\n          },\n        ),\n        const SizedBox(height: 12),\n        Text(\n          _selezionato == null\n              ? 'Nessuna selezione'\n              : 'Hai scelto: $_selezionato',\n        ),\n      ],\n    );\n  }\n}","Toccando il dropdown si apre l'elenco dei cinque linguaggi; dopo la selezione il testo sotto mostra la voce scelta.",{"id":50,"position":51,"title":52,"body":53,"code_snippet":54,"code_language":39,"expected_result":55,"demo_url":41,"video_url":41},785,3,"Personalizzare l'aspetto del dropdown","Il `DropdownButton` di default è piuttosto spartano. Ecco le proprietà che userai più spesso:\n\n- **`isExpanded: true`**: fa occupare al dropdown tutta la larghezza disponibile (evita anche gli overflow con testi lunghi);\n- **`icon`**, **`iconSize`**, **`iconEnabledColor`**: personalizzano la freccia;\n- **`underline`**: sostituisce (o rimuove, con un `SizedBox.shrink()`) la linea in basso;\n- **`borderRadius`** e **`dropdownColor`**: aspetto del menu che si apre;\n- **`menuMaxHeight`**: limita l'altezza quando le voci sono tante.\n\nSe vuoi un bordo completo, il trucco più semplice è avvolgere il dropdown in un `InputDecorator` oppure usare direttamente `DropdownButtonFormField`, che vedremo nel passo successivo.\n\nNota anche `DropdownButtonHideUnderline`: utile quando inserisci il dropdown dentro un contenitore già decorato.","Container(\n  padding: const EdgeInsets.symmetric(horizontal: 12),\n  decoration: BoxDecoration(\n    border: Border.all(color: Colors.grey.shade400),\n    borderRadius: BorderRadius.circular(12),\n  ),\n  child: DropdownButtonHideUnderline(\n    child: DropdownButton\u003CString>(\n      value: _selezionato,\n      isExpanded: true,\n      hint: const Text('Scegli un linguaggio'),\n      icon: const Icon(Icons.expand_more),\n      borderRadius: BorderRadius.circular(12),\n      menuMaxHeight: 300,\n      items: kLinguaggi.map((String linguaggio) {\n        return DropdownMenuItem\u003CString>(\n          value: linguaggio,\n          child: Row(\n            children: [\n              const Icon(Icons.code, size: 18),\n              const SizedBox(width: 8),\n              Text(linguaggio),\n            ],\n          ),\n        );\n      }).toList(),\n      onChanged: (String? nuovo) => setState(() => _selezionato = nuovo),\n    ),\n  ),\n)","Il dropdown occupa tutta la larghezza, ha un bordo arrotondato, nessuna underline e ogni voce mostra un'icona accanto al testo.",{"id":57,"position":58,"title":59,"body":60,"code_snippet":61,"code_language":39,"expected_result":62,"demo_url":41,"video_url":41},786,4,"Dropdown dentro un Form con validazione","Quando il menu a tendina fa parte di un modulo, usa **`DropdownButtonFormField`**: eredita tutte le opzioni di `DropdownButton` e in più supporta `decoration` (come un `TextFormField`), `validator` e il salvataggio con `FormState`.\n\nPunti chiave:\n\n- il valore iniziale si passa con `initialValue` (nelle versioni meno recenti di Flutter la proprietà si chiama `value`: se l'IDE segnala un errore, usa quella);\n- il `validator` restituisce `null` se il valore è valido, altrimenti il messaggio di errore;\n- `_formKey.currentState!.validate()` esegue la validazione di tutti i campi del form.\n\nIn questo modo l'errore viene mostrato sotto il campo, esattamente come per un `TextFormField`.","class IscrizioneForm extends StatefulWidget {\n  const IscrizioneForm({super.key});\n\n  @override\n  State\u003CIscrizioneForm> createState() => _IscrizioneFormState();\n}\n\nclass _IscrizioneFormState extends State\u003CIscrizioneForm> {\n  final _formKey = GlobalKey\u003CFormState>();\n  String? _linguaggio;\n\n  void _invia() {\n    if (_formKey.currentState!.validate()) {\n      ScaffoldMessenger.of(context).showSnackBar(\n        SnackBar(content: Text('Iscrizione inviata: $_linguaggio')),\n      );\n    }\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return Form(\n      key: _formKey,\n      child: Column(\n        children: [\n          DropdownButtonFormField\u003CString>(\n            initialValue: _linguaggio,\n            isExpanded: true,\n            decoration: const InputDecoration(\n              labelText: 'Linguaggio preferito',\n              prefixIcon: Icon(Icons.terminal),\n              border: OutlineInputBorder(),\n            ),\n            items: kLinguaggi\n                .map((l) => DropdownMenuItem(value: l, child: Text(l)))\n                .toList(),\n            validator: (value) =>\n                value == null ? 'Seleziona un linguaggio' : null,\n            onChanged: (value) => setState(() => _linguaggio = value),\n          ),\n          const SizedBox(height: 16),\n          ElevatedButton(\n            onPressed: _invia,\n            child: const Text('Invia'),\n          ),\n        ],\n      ),\n    );\n  }\n}","Premendo \"Invia\" senza aver scelto nulla compare in rosso \"Seleziona un linguaggio\"; dopo la selezione la SnackBar mostra il valore scelto.",{"id":64,"position":65,"title":66,"body":67,"code_snippet":68,"code_language":39,"expected_result":69,"demo_url":41,"video_url":41},787,5,"Il DropdownMenu di Material 3 con filtro e ricerca","`DropdownMenu` è il widget introdotto con Material 3: unisce un campo di testo a un menu a tendina, quindi l'utente può **digitare** per filtrare le voci.\n\nDifferenze principali rispetto a `DropdownButton`:\n\n- le voci si passano in `dropdownMenuEntries` come `DropdownMenuEntry` (con `value`, `label` e, opzionalmente, `leadingIcon`);\n- la selezione si legge da `onSelected`;\n- `enableFilter: true` filtra le voci mentre si scrive, `enableSearch` (attivo di default) evidenzia la prima corrispondenza;\n- `requestFocusOnTap: false` lo rende di sola selezione, senza tastiera (utile su mobile).\n\nÈ ottimo per elenchi lunghi come paesi, valute o categorie.","class PaesePicker extends StatefulWidget {\n  const PaesePicker({super.key});\n\n  @override\n  State\u003CPaesePicker> createState() => _PaesePickerState();\n}\n\nclass _PaesePickerState extends State\u003CPaesePicker> {\n  static const paesi = \u003CString>[\n    'Italia', 'Francia', 'Germania', 'Spagna', 'Portogallo', 'Grecia',\n  ];\n\n  String? _paese;\n\n  @override\n  Widget build(BuildContext context) {\n    return Column(\n      crossAxisAlignment: CrossAxisAlignment.start,\n      children: [\n        DropdownMenu\u003CString>(\n          width: double.infinity,\n          label: const Text('Paese'),\n          leadingIcon: const Icon(Icons.public),\n          enableFilter: true,\n          menuHeight: 280,\n          initialSelection: _paese,\n          onSelected: (String? valore) => setState(() => _paese = valore),\n          dropdownMenuEntries: paesi\n              .map((p) => DropdownMenuEntry\u003CString>(value: p, label: p))\n              .toList(),\n        ),\n        const SizedBox(height: 8),\n        Text('Paese selezionato: ${_paese ?? \"-\"}'),\n      ],\n    );\n  }\n}","Digitando \"ita\" nel campo l'elenco si riduce a \"Italia\"; selezionandola il testo sotto si aggiorna.",{"id":71,"position":72,"title":73,"body":74,"code_snippet":75,"code_language":39,"expected_result":76,"demo_url":41,"video_url":41},788,6,"Due dropdown collegati: regione e provincia","Un classico dei form reali: il secondo menu dipende dalla scelta fatta nel primo.\n\nIl pattern è semplice ma ha una **trappola** molto comune: quando cambi la regione devi **azzerare la provincia**, altrimenti il `value` del secondo dropdown non esisterà più nella nuova lista di `items` e l'app andrà in errore con un'asserzione.\n\nNota anche che il secondo dropdown passa `onChanged: null` finché non è stata scelta una regione: è il modo idiomatico per disabilitarlo.","class RegioneProvincia extends StatefulWidget {\n  const RegioneProvincia({super.key});\n\n  @override\n  State\u003CRegioneProvincia> createState() => _RegioneProvinciaState();\n}\n\nclass _RegioneProvinciaState extends State\u003CRegioneProvincia> {\n  static const Map\u003CString, List\u003CString>> dati = {\n    'Lombardia': ['Milano', 'Bergamo', 'Brescia'],\n    'Lazio': ['Roma', 'Latina', 'Viterbo'],\n    'Sicilia': ['Palermo', 'Catania', 'Messina'],\n  };\n\n  String? _regione;\n  String? _provincia;\n\n  @override\n  Widget build(BuildContext context) {\n    final province = _regione == null ? \u003CString>[] : dati[_regione]!;\n\n    return Column(\n      children: [\n        DropdownButtonFormField\u003CString>(\n          initialValue: _regione,\n          isExpanded: true,\n          decoration: const InputDecoration(\n            labelText: 'Regione',\n            border: OutlineInputBorder(),\n          ),\n          items: dati.keys\n              .map((r) => DropdownMenuItem(value: r, child: Text(r)))\n              .toList(),\n          onChanged: (valore) {\n            setState(() {\n              _regione = valore;\n              _provincia = null; \u002F\u002F fondamentale: azzera la scelta precedente\n            });\n          },\n        ),\n        const SizedBox(height: 16),\n        DropdownButtonFormField\u003CString>(\n          initialValue: _provincia,\n          isExpanded: true,\n          decoration: const InputDecoration(\n            labelText: 'Provincia',\n            border: OutlineInputBorder(),\n          ),\n          items: province\n              .map((p) => DropdownMenuItem(value: p, child: Text(p)))\n              .toList(),\n          onChanged: _regione == null\n              ? null \u002F\u002F disabilitato finché non scegli la regione\n              : (valore) => setState(() => _provincia = valore),\n        ),\n      ],\n    );\n  }\n}","Il dropdown delle province è grigio e non cliccabile finché non si sceglie una regione; cambiando regione la provincia si svuota e mostra le nuove voci.",{"id":78,"position":79,"title":80,"body":81,"code_snippet":82,"code_language":39,"expected_result":83,"demo_url":41,"video_url":41},789,7,"Errori frequenti e buone pratiche","Prima di chiudere, i problemi che incontrerai più spesso e come risolverli:\n\n1. **\"There should be exactly one item with DropdownButton's value\"** — il `value` non è presente negli `items`, oppure ci sono voci duplicate. Soluzione: azzera il valore quando cambi la lista (come nel passo precedente) e assicurati che i `value` siano univoci.\n2. **Il menu non si aggiorna dopo la selezione** — hai dimenticato `setState` dentro `onChanged`, oppure stai usando uno `StatelessWidget`.\n3. **Overflow del testo** — imposta `isExpanded: true` e, sulle voci lunghe, `Text(..., overflow: TextOverflow.ellipsis)`.\n4. **Oggetti invece di stringhe** — puoi usare qualsiasi tipo come `value` (anche un `enum` o un modello). Se usi classi personalizzate, implementa `==` e `hashCode` (o usa un `enum`), perché Flutter confronta i valori per uguaglianza.\n5. **Liste molto lunghe** — preferisci `DropdownMenu` con `enableFilter: true`, molto più comodo per l'utente.\n\nQui sotto un esempio con un `enum`, l'approccio più sicuro e leggibile quando le opzioni sono fisse.","enum Spedizione {\n  standard('Standard - 5 giorni'),\n  express('Express - 24 ore'),\n  ritiro('Ritiro in negozio');\n\n  const Spedizione(this.etichetta);\n  final String etichetta;\n}\n\n\u002F\u002F Dentro uno StatefulWidget:\n\u002F\u002F Spedizione _spedizione = Spedizione.standard;\n\nDropdownButton\u003CSpedizione>(\n  value: _spedizione,\n  isExpanded: true,\n  items: Spedizione.values\n      .map((s) => DropdownMenuItem(\n            value: s,\n            child: Text(s.etichetta, overflow: TextOverflow.ellipsis),\n          ))\n      .toList(),\n  onChanged: (valore) {\n    if (valore != null) setState(() => _spedizione = valore);\n  },\n)","Il dropdown mostra le tre modalità di spedizione e il valore selezionato è tipizzato (`Spedizione`), senza stringhe magiche nel codice.",[],1792316927323]