[{"data":1,"prerenderedAt":78},["ShallowReactive",2],{"tutorial-feedback-allutente-in-flutter-snackbar-alertdialog-e-scaffoldmessenger":3,"comments-tutorial-feedback-allutente-in-flutter-snackbar-alertdialog-e-scaffoldmessenger":77},{"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},82,"Feedback all'utente in Flutter: SnackBar, AlertDialog e ScaffoldMessenger","feedback-allutente-in-flutter-snackbar-alertdialog-e-scaffoldmessenger","Impara a comunicare con l'utente in Flutter: messaggi temporanei con SnackBar e ScaffoldMessenger, conferme con AlertDialog, azioni annullabili e indicatori di caricamento bloccanti.","# Parlare con l'utente: messaggi, conferme e avvisi\n\nUn'app ben fatta non si limita a fare le cose: **le comunica**. Quando l'utente salva un dato, elimina un elemento o commette un errore, ha bisogno di un riscontro immediato.\n\nFlutter mette a disposizione due strumenti fondamentali e velocissimi da usare:\n\n- **SnackBar**: un messaggio temporaneo che compare in basso, ideale per conferme non bloccanti (\"Elemento salvato\") e per offrire un'azione di annullamento.\n- **AlertDialog**: una finestra modale che blocca l'interazione finché l'utente non risponde, perfetta per confermare operazioni distruttive (\"Vuoi davvero eliminare?\").\n\nIn questa guida costruiremo una piccola schermata con una lista di note dove:\n\n1. mostreremo una SnackBar dopo un salvataggio;\n2. aggiungeremo un pulsante **ANNULLA** alla SnackBar;\n3. chiederemo conferma con un AlertDialog prima di eliminare;\n4. mostreremo un dialog di caricamento non chiudibile.\n\n> **Prerequisiti**: conoscere `StatefulWidget`, `setState` e la struttura base di uno `Scaffold`. Non serve alcun pacchetto esterno: usiamo solo il Material Design incluso in Flutter.","https:\u002F\u002Fflutter.it\u002Fstorage\u002Ftutorials\u002Fa93312d4-cb6d-4346-904e-90085ac05f0c.jpg","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1778585035869-6b0b60c2e33a?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzA2NTJ8MHwxfHJhbmRvbXx8fHx8fHx8fDE3ODgzMjM0NzZ8&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\u002Fphone-cleaner-app-showing-finished-cleaning-and-whatsapp-cleaner-pZepq9rdIzc","https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=Esb6sZSr_Ag",{"youtube_id":17,"duration_seconds":18,"published_at":19},"Esb6sZSr_Ag",119,"2026-09-02T09:09:53+00:00","beginner",30,"3.x","published","2026-09-02T04:31:17+00:00","SnackBar e AlertDialog in Flutter: guida base","Guida beginner a SnackBar, ScaffoldMessenger e AlertDialog in Flutter: messaggi temporanei, azione annulla, dialoghi di conferma e di caricamento.",{"id":28,"name":29,"slug":30},1,"Guide","guide",{"id":28,"name":32},"Flutter Bot",[34,42,49,56,63,70],{"id":35,"position":28,"title":36,"body":37,"code_snippet":38,"code_language":39,"expected_result":40,"demo_url":41,"video_url":41},547,"Preparare la schermata di base con una lista di note","Partiamo da uno `StatefulWidget` con una lista di stringhe in memoria. È la base su cui innesteremo i messaggi.\n\nPunti chiave:\n\n- la lista `_note` è lo **stato** della schermata: ogni modifica passa da `setState`;\n- il `FloatingActionButton` aggiunge una nota;\n- ogni `ListTile` ha un'icona per eliminare l'elemento.\n\nSostituisci il contenuto di `lib\u002Fmain.dart` con il codice qui sotto e lancia l'app: per ora non compare ancora nessun messaggio.","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: 'Feedback utente',\n      theme: ThemeData(\n        colorSchemeSeed: Colors.indigo,\n        useMaterial3: true,\n      ),\n      home: const NotePage(),\n    );\n  }\n}\n\nclass NotePage extends StatefulWidget {\n  const NotePage({super.key});\n\n  @override\n  State\u003CNotePage> createState() => _NotePageState();\n}\n\nclass _NotePageState extends State\u003CNotePage> {\n  final List\u003CString> _note = \u003CString>['Comprare il latte', 'Chiamare Luca'];\n  int _contatore = 3;\n\n  void _aggiungiNota() {\n    setState(() {\n      _note.add('Nota numero $_contatore');\n      _contatore++;\n    });\n  }\n\n  void _eliminaNota(int index) {\n    setState(() {\n      _note.removeAt(index);\n    });\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(title: const Text('Le mie note')),\n      body: ListView.builder(\n        itemCount: _note.length,\n        itemBuilder: (context, index) {\n          return ListTile(\n            leading: const Icon(Icons.sticky_note_2_outlined),\n            title: Text(_note[index]),\n            trailing: IconButton(\n              icon: const Icon(Icons.delete_outline),\n              onPressed: () => _eliminaNota(index),\n            ),\n          );\n        },\n      ),\n      floatingActionButton: FloatingActionButton(\n        onPressed: _aggiungiNota,\n        child: const Icon(Icons.add),\n      ),\n    );\n  }\n}","dart","L'app mostra una lista di due note: il pulsante + ne aggiunge di nuove e l'icona cestino le rimuove, ma senza alcun messaggio di conferma.",null,{"id":43,"position":44,"title":45,"body":46,"code_snippet":47,"code_language":39,"expected_result":48,"demo_url":41,"video_url":41},548,2,"Mostrare una SnackBar con ScaffoldMessenger","La SnackBar si mostra tramite `ScaffoldMessenger.of(context).showSnackBar(...)`. `ScaffoldMessenger` è l'oggetto (introdotto in Flutter 2) che gestisce la coda dei messaggi: usarlo evita l'errore classico `Scaffold.of() called with a context that does not contain a Scaffold`.\n\nAlcune proprietà utili:\n\n- `content`: il widget da mostrare, di solito un `Text`;\n- `duration`: quanto resta a schermo (default 4 secondi);\n- `behavior: SnackBarBehavior.floating`: la fa \"galleggiare\" sopra il contenuto;\n- `hideCurrentSnackBar()`: chiude subito il messaggio in coda, utile se l'utente tocca il pulsante più volte di seguito.\n\nAggiungiamo un metodo `_mostraMessaggio` e chiamiamolo quando si aggiunge una nota.","void _mostraMessaggio(String testo) {\n  final messenger = ScaffoldMessenger.of(context);\n  messenger.hideCurrentSnackBar(); \u002F\u002F evita l'accumulo di messaggi\n  messenger.showSnackBar(\n    SnackBar(\n      content: Text(testo),\n      duration: const Duration(seconds: 2),\n      behavior: SnackBarBehavior.floating,\n      margin: const EdgeInsets.all(16),\n      shape: RoundedRectangleBorder(\n        borderRadius: BorderRadius.circular(12),\n      ),\n    ),\n  );\n}\n\nvoid _aggiungiNota() {\n  setState(() {\n    _note.add('Nota numero $_contatore');\n    _contatore++;\n  });\n  _mostraMessaggio('Nota aggiunta con successo');\n}","Premendo il pulsante + compare in basso una SnackBar arrotondata con il testo \"Nota aggiunta con successo\", che scompare dopo 2 secondi.",{"id":50,"position":51,"title":52,"body":53,"code_snippet":54,"code_language":39,"expected_result":55,"demo_url":41,"video_url":41},549,3,"Aggiungere l'azione ANNULLA alla SnackBar","La SnackBar accetta una `SnackBarAction`: un pulsante testuale a destra del messaggio. È il pattern standard per l'**undo** di un'eliminazione: si rimuove subito l'elemento e si dà all'utente qualche secondo per ripristinarlo.\n\nModifichiamo `_eliminaNota` in modo che:\n\n1. salvi il testo e la posizione dell'elemento rimosso;\n2. mostri una SnackBar con l'azione `ANNULLA`;\n3. reinserisca la nota nella posizione originale se l'utente tocca il pulsante.\n\nNota `insert(index, nota)`: rimette l'elemento esattamente dov'era, non in fondo alla lista.","void _eliminaNota(int index) {\n  final notaRimossa = _note[index];\n  final posizione = index;\n\n  setState(() {\n    _note.removeAt(index);\n  });\n\n  final messenger = ScaffoldMessenger.of(context);\n  messenger.hideCurrentSnackBar();\n  messenger.showSnackBar(\n    SnackBar(\n      content: Text('\"$notaRimossa\" eliminata'),\n      duration: const Duration(seconds: 4),\n      behavior: SnackBarBehavior.floating,\n      margin: const EdgeInsets.all(16),\n      action: SnackBarAction(\n        label: 'ANNULLA',\n        onPressed: () {\n          setState(() {\n            _note.insert(posizione, notaRimossa);\n          });\n        },\n      ),\n    ),\n  );\n}","Eliminando una nota compare la SnackBar con il pulsante ANNULLA: toccandolo entro 4 secondi la nota torna nella stessa posizione della lista.",{"id":57,"position":58,"title":59,"body":60,"code_snippet":61,"code_language":39,"expected_result":62,"demo_url":41,"video_url":41},550,4,"Chiedere conferma con un AlertDialog","Per le operazioni potenzialmente distruttive (come lo svuotamento della lista) serve una conferma esplicita. Si usa `showDialog`, che restituisce un `Future` con il valore passato a `Navigator.pop(context, valore)`.\n\nPunti importanti:\n\n- il dialog si chiude con `Navigator.pop(context, true\u002Ffalse)`;\n- `showDialog\u003Cbool>` è **asincrono**: si aspetta la risposta con `await`;\n- se l'utente tocca fuori dal dialog, il risultato è `null`: per questo usiamo `confermato == true`;\n- dopo un `await` il widget potrebbe non essere più a schermo: controlliamo `if (!mounted) return;` prima di usare di nuovo il `context`.\n\nAggiungiamo un pulsante nell'AppBar che svuota la lista previa conferma.","Future\u003Cbool> _chiediConferma() async {\n  final confermato = await showDialog\u003Cbool>(\n    context: context,\n    builder: (dialogContext) {\n      return AlertDialog(\n        title: const Text('Eliminare tutte le note?'),\n        content: const Text(\n          'Questa operazione non può essere annullata.',\n        ),\n        actions: [\n          TextButton(\n            onPressed: () => Navigator.pop(dialogContext, false),\n            child: const Text('Annulla'),\n          ),\n          FilledButton(\n            onPressed: () => Navigator.pop(dialogContext, true),\n            child: const Text('Elimina'),\n          ),\n        ],\n      );\n    },\n  );\n  return confermato ?? false; \u002F\u002F tocco fuori dal dialog = annullato\n}\n\nFuture\u003Cvoid> _svuotaLista() async {\n  final conferma = await _chiediConferma();\n  if (!mounted) return;\n  if (conferma) {\n    setState(() => _note.clear());\n    _mostraMessaggio('Tutte le note sono state eliminate');\n  }\n}\n\n\u002F\u002F Nell'AppBar del metodo build:\n\u002F\u002F AppBar(\n\u002F\u002F   title: const Text('Le mie note'),\n\u002F\u002F   actions: [\n\u002F\u002F     IconButton(\n\u002F\u002F       icon: const Icon(Icons.delete_sweep_outlined),\n\u002F\u002F       onPressed: _svuotaLista,\n\u002F\u002F     ),\n\u002F\u002F   ],\n\u002F\u002F )","Toccando l'icona nell'AppBar compare una finestra modale: con \"Annulla\" non succede nulla, con \"Elimina\" la lista si svuota e appare la SnackBar di conferma.",{"id":64,"position":65,"title":66,"body":67,"code_snippet":68,"code_language":39,"expected_result":69,"demo_url":41,"video_url":41},551,5,"Un dialog di caricamento non chiudibile","Durante un'operazione lunga (una chiamata di rete, un salvataggio) può servire un dialog bloccante con uno spinner. Si ottiene con `barrierDismissible: false` e un `PopScope` che impedisce la chiusura con il tasto indietro di Android.\n\nRegola d'oro: **chi apre il dialog deve chiuderlo**. Qui lo apriamo prima dell'operazione e lo chiudiamo subito dopo con `Navigator.pop`, usando `rootNavigator: true` per essere certi di rimuovere la route del dialog.\n\nSimuliamo un salvataggio di 2 secondi che al termine mostra una SnackBar verde.","void _mostraCaricamento() {\n  showDialog\u003Cvoid>(\n    context: context,\n    barrierDismissible: false,\n    builder: (_) => const PopScope(\n      canPop: false,\n      child: AlertDialog(\n        content: Row(\n          children: [\n            CircularProgressIndicator(),\n            SizedBox(width: 20),\n            Expanded(child: Text('Salvataggio in corso...')),\n          ],\n        ),\n      ),\n    ),\n  );\n}\n\nFuture\u003Cvoid> _salvaSulServer() async {\n  _mostraCaricamento();\n\n  \u002F\u002F Simulazione di un'operazione lunga\n  await Future\u003Cvoid>.delayed(const Duration(seconds: 2));\n\n  if (!mounted) return;\n  Navigator.of(context, rootNavigator: true).pop(); \u002F\u002F chiude il dialog\n\n  ScaffoldMessenger.of(context).showSnackBar(\n    SnackBar(\n      content: const Text('Note sincronizzate!'),\n      backgroundColor: Colors.green.shade700,\n      behavior: SnackBarBehavior.floating,\n      margin: const EdgeInsets.all(16),\n    ),\n  );\n}\n\n\u002F\u002F Nell'AppBar, accanto agli altri actions:\n\u002F\u002F IconButton(\n\u002F\u002F   icon: const Icon(Icons.cloud_upload_outlined),\n\u002F\u002F   onPressed: _salvaSulServer,\n\u002F\u002F ),","Toccando l'icona di upload appare per 2 secondi un dialog con spinner non chiudibile; poi si chiude da solo e compare una SnackBar verde \"Note sincronizzate!\".",{"id":71,"position":72,"title":73,"body":74,"code_snippet":75,"code_language":39,"expected_result":76,"demo_url":41,"video_url":41},552,6,"Ripulire il codice: un helper riutilizzabile","Ripetere `ScaffoldMessenger.of(context).showSnackBar(...)` in ogni schermata è scomodo. Estraiamo la logica in una **extension** su `BuildContext`: da quel momento in tutta l'app basterà scrivere `context.mostraSnack('Salvato!')`.\n\nCrea il file `lib\u002Futils\u002Ffeedback_ui.dart` e importalo dove serve. Con `isErrore: true` ottieni un messaggio in rosso, utile per gli errori.\n\n**Buone pratiche finali:**\n\n- SnackBar per informazioni non critiche, AlertDialog solo quando serve una decisione;\n- testi brevi e in una sola riga: la SnackBar non è il posto per uno stack trace;\n- una sola azione per SnackBar (di solito \"Annulla\");\n- controlla sempre `mounted` dopo un `await` prima di riusare il `context`.","import 'package:flutter\u002Fmaterial.dart';\n\nextension FeedbackUI on BuildContext {\n  void mostraSnack(String messaggio, {bool isErrore = false}) {\n    final messenger = ScaffoldMessenger.of(this);\n    messenger.hideCurrentSnackBar();\n    messenger.showSnackBar(\n      SnackBar(\n        content: Text(messaggio),\n        behavior: SnackBarBehavior.floating,\n        margin: const EdgeInsets.all(16),\n        backgroundColor: isErrore ? Colors.red.shade700 : null,\n        shape: RoundedRectangleBorder(\n          borderRadius: BorderRadius.circular(12),\n        ),\n      ),\n    );\n  }\n\n  Future\u003Cbool> chiediConferma({\n    required String titolo,\n    required String messaggio,\n    String testoOk = 'Conferma',\n    String testoAnnulla = 'Annulla',\n  }) async {\n    final risposta = await showDialog\u003Cbool>(\n      context: this,\n      builder: (ctx) => AlertDialog(\n        title: Text(titolo),\n        content: Text(messaggio),\n        actions: [\n          TextButton(\n            onPressed: () => Navigator.pop(ctx, false),\n            child: Text(testoAnnulla),\n          ),\n          FilledButton(\n            onPressed: () => Navigator.pop(ctx, true),\n            child: Text(testoOk),\n          ),\n        ],\n      ),\n    );\n    return risposta ?? false;\n  }\n}\n\n\u002F\u002F Uso nella pagina:\n\u002F\u002F context.mostraSnack('Nota aggiunta con successo');\n\u002F\u002F final ok = await context.chiediConferma(\n\u002F\u002F   titolo: 'Eliminare tutte le note?',\n\u002F\u002F   messaggio: 'Questa operazione non può essere annullata.',\n\u002F\u002F   testoOk: 'Elimina',\n\u002F\u002F );","L'app funziona come prima, ma il codice delle schermate è più pulito: una riga per mostrare un messaggio e una per chiedere conferma, riutilizzabili ovunque.",[],1789120598525]