[{"data":1,"prerenderedAt":85},["ShallowReactive",2],{"tutorial-statelesswidget-statefulwidget-e-setstate-capire-lo-stato-in-flutter":3,"comments-tutorial-statelesswidget-statefulwidget-e-setstate-capire-lo-stato-in-flutter":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},77,"StatelessWidget, StatefulWidget e setState: capire lo stato in Flutter","statelesswidget-statefulwidget-e-setstate-capire-lo-stato-in-flutter","Impara la differenza tra StatelessWidget e StatefulWidget in Flutter e usa setState per aggiornare l'interfaccia: esempi pratici, contatore, form e errori tipici da evitare.","In Flutter **tutto è un widget**: testi, bottoni, padding e persino l'app stessa. Ma non tutti i widget sono uguali: alcuni descrivono una porzione di interfaccia che non cambia mai (`StatelessWidget`), altri devono poter cambiare aspetto nel tempo in risposta a un tocco, a un dato che arriva dalla rete o al passare di un timer (`StatefulWidget`).\n\nCapire questa distinzione — e sapere **quando e come** chiamare `setState()` — è il passaggio più importante per chi inizia con Flutter. È anche la fonte dei dubbi più frequenti: *perché la mia UI non si aggiorna?*, *perché il valore torna sempre a zero?*, *dove dichiaro le variabili?*\n\nIn questo tutorial costruiremo passo passo una piccola app che parte da un widget statico e arriva a un contatore interattivo con controlli abilitati\u002Fdisabilitati, imparando lungo la strada:\n\n- la differenza concreta tra `StatelessWidget` e `StatefulWidget`;\n- il ruolo della classe `State` e del metodo `build()`;\n- come funziona `setState()` e cosa succede realmente quando lo chiami;\n- come passare dati da un widget padre a un figlio con i parametri del costruttore;\n- gli errori più comuni (`setState` chiamato dopo `dispose`, variabili dichiarate nel posto sbagliato, logica pesante dentro `setState`).\n\nServe solo un progetto Flutter appena creato con `flutter create` e qualche minuto di tempo.","https:\u002F\u002Fflutter.it\u002Fstorage\u002Ftutorials\u002F4637c983-0fc4-45f2-8ded-25e02d20bf5a.jpg","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1519336367661-eba9c1dfa5e9?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzA2NTJ8MHwxfHJhbmRvbXx8fHx8fHx8fDE3ODc4OTE0OTF8&ixlib=rb-4.1.0&q=80&w=1080",{"name":12,"author_url":13,"photo_url":14},"freestocks","https:\u002F\u002Funsplash.com\u002F@freestocks","https:\u002F\u002Funsplash.com\u002Fphotos\u002Fperson-holding-space-grey-iphone-x-turn-on-40k6ZqbsXuo","https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=amH5KliEsDU",{"youtube_id":17,"duration_seconds":18,"published_at":19},"amH5KliEsDU",139,"2026-08-28T17:01:54+00:00","beginner",35,"3.x","published","2026-08-28T04:31:31+00:00","StatelessWidget, StatefulWidget e setState in Flutter","Guida beginner alla differenza tra StatelessWidget e StatefulWidget in Flutter e all'uso corretto di setState per aggiornare l'interfaccia.",{"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},514,"Creare il progetto e ripulire main.dart","Partiamo da un progetto pulito. Da terminale:\n\n```bash\nflutter create stato_demo\ncd stato_demo\n```\n\nApri `lib\u002Fmain.dart` e sostituisci tutto il contenuto con il codice qui sotto: una `MaterialApp` minimale con una `HomePage` ancora vuota. Nota che `MyApp` è un **StatelessWidget**: descrive la configurazione dell'app, che non cambia mai durante l'esecuzione.\n\nIl metodo `build()` di uno `StatelessWidget` riceve il `BuildContext` e restituisce l'albero dei widget da disegnare. Viene chiamato da Flutter ogni volta che c'è bisogno di ridisegnare quella porzione di schermo: per questo **non deve contenere logica pesante**, solo la descrizione della UI.","import 'package:flutter\u002Fmaterial.dart';\n\nvoid main() {\n  runApp(const MyApp());\n}\n\nclass MyApp extends StatelessWidget {\n  const MyApp({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp(\n      title: 'Stato Demo',\n      theme: ThemeData(\n        colorSchemeSeed: Colors.indigo,\n        useMaterial3: true,\n      ),\n      home: const HomePage(),\n    );\n  }\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('Stato in Flutter')),\n      body: const Center(child: Text('Ciao Flutter!')),\n    );\n  }\n}","dart","Lanciando `flutter run` vedi una schermata con una AppBar viola e la scritta \"Ciao Flutter!\" al centro.",null,{"id":43,"position":44,"title":45,"body":46,"code_snippet":47,"code_language":39,"expected_result":48,"demo_url":41,"video_url":41},515,2,"Costruire un StatelessWidget riutilizzabile con parametri","Uno `StatelessWidget` non è \"un widget che non fa niente\": è un widget il cui aspetto dipende **solo** dai dati che riceve dall'esterno, cioè dai parametri del costruttore. Se quei dati cambiano, il padre ricostruisce il figlio con nuovi valori.\n\nCreiamo `lib\u002Fwidgets\u002Fbadge_valore.dart`: un riquadro che mostra un'etichetta e un valore. I suoi campi sono `final` — è una regola: **tutti i campi di uno StatelessWidget devono essere immutabili**. Se provi a modificarli dall'interno, il widget non si aggiornerà comunque, perché Flutter non sa che qualcosa è cambiato.\n\nNota il costruttore `const`: permette a Flutter di riutilizzare l'istanza e saltare ricostruzioni inutili, un piccolo ma gratuito guadagno di performance.","import 'package:flutter\u002Fmaterial.dart';\n\nclass BadgeValore extends StatelessWidget {\n  const BadgeValore({\n    super.key,\n    required this.etichetta,\n    required this.valore,\n    this.colore = Colors.indigo,\n  });\n\n  final String etichetta;\n  final int valore;\n  final Color colore;\n\n  @override\n  Widget build(BuildContext context) {\n    return Container(\n      padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),\n      decoration: BoxDecoration(\n        color: colore.withOpacity(0.1),\n        borderRadius: BorderRadius.circular(16),\n        border: Border.all(color: colore, width: 2),\n      ),\n      child: Column(\n        mainAxisSize: MainAxisSize.min,\n        children: [\n          Text(\n            etichetta,\n            style: TextStyle(color: colore, fontWeight: FontWeight.w600),\n          ),\n          const SizedBox(height: 8),\n          Text(\n            '$valore',\n            style: Theme.of(context).textTheme.displaySmall?.copyWith(\n                  color: colore,\n                  fontWeight: FontWeight.bold,\n                ),\n          ),\n        ],\n      ),\n    );\n  }\n}","Hai un widget riutilizzabile `BadgeValore(etichetta: 'Totale', valore: 3)` che compila senza errori.",{"id":50,"position":51,"title":52,"body":53,"code_snippet":54,"code_language":39,"expected_result":55,"demo_url":41,"video_url":41},516,3,"Trasformare la HomePage in uno StatefulWidget","Ora serve un widget che ricordi qualcosa tra una ricostruzione e l'altra: il numero di click. Convertiamo `HomePage` in `StatefulWidget`.\n\nUno `StatefulWidget` è composto da **due classi**:\n\n1. la classe del widget (immutabile, con i parametri in ingresso) che implementa `createState()`;\n2. la classe `State`, dove vivono le **variabili mutabili** e il metodo `build()`.\n\nQuesta separazione esiste perché il widget viene distrutto e ricreato a ogni ricostruzione dell'albero, mentre l'oggetto `State` **sopravvive** ed è il posto giusto dove conservare i dati.\n\n> Suggerimento: in VS Code posiziona il cursore sul nome della classe, premi `Ctrl+.` (o `Cmd+.` su macOS) e scegli *Convert to StatefulWidget*. Android Studio offre lo stesso refactoring.\n\nPer ora la variabile `_contatore` esiste ma non cambia ancora: il bottone stampa solo in console.","class HomePage extends StatefulWidget {\n  const HomePage({super.key});\n\n  @override\n  State\u003CHomePage> createState() => _HomePageState();\n}\n\nclass _HomePageState extends State\u003CHomePage> {\n  \u002F\u002F Lo stato vive QUI, non dentro build().\n  int _contatore = 0;\n\n  void _incrementa() {\n    _contatore++; \u002F\u002F ancora senza setState: la UI non si aggiornerà!\n    debugPrint('Contatore ora vale: $_contatore');\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(title: const Text('Stato in Flutter')),\n      body: Center(\n        child: BadgeValore(etichetta: 'Click', valore: _contatore),\n      ),\n      floatingActionButton: FloatingActionButton(\n        onPressed: _incrementa,\n        child: const Icon(Icons.add),\n      ),\n    );\n  }\n}","Premendo il bottone la console mostra \"Contatore ora vale: 1, 2, 3...\" ma il numero a schermo resta fermo su 0: è la dimostrazione che modificare una variabile non basta.",{"id":57,"position":58,"title":59,"body":60,"code_snippet":61,"code_language":39,"expected_result":62,"demo_url":41,"video_url":41},517,4,"Aggiornare l'interfaccia con setState()","Flutter non \"osserva\" le tue variabili: devi essere tu a dirgli che lo stato è cambiato. Lo fai chiamando `setState()`, che:\n\n1. esegue la funzione che gli passi (dove modifichi le variabili);\n2. marca l'oggetto `State` come *dirty*;\n3. schedula una nuova chiamata a `build()` al prossimo frame.\n\nRegole pratiche:\n\n- **modifica le variabili dentro** la callback di `setState`, non fuori;\n- tieni la callback **sincrona e leggerissima**: niente `await`, niente chiamate di rete;\n- se un'operazione è asincrona, fai prima `await`, poi chiama `setState` solo per assegnare il risultato.\n\nAggiungiamo anche un pulsante per decrementare e uno per azzerare, così vedi che `setState` può contenere qualsiasi modifica di stato.","class _HomePageState extends State\u003CHomePage> {\n  int _contatore = 0;\n\n  void _incrementa() {\n    setState(() {\n      _contatore++;\n    });\n  }\n\n  void _decrementa() {\n    if (_contatore == 0) return; \u002F\u002F niente da fare, evitiamo un rebuild inutile\n    setState(() {\n      _contatore--;\n    });\n  }\n\n  void _azzera() {\n    setState(() => _contatore = 0);\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    final raggiunto = _contatore >= 10;\n\n    return Scaffold(\n      appBar: AppBar(\n        title: const Text('Stato in Flutter'),\n        actions: [\n          IconButton(\n            onPressed: _contatore == 0 ? null : _azzera,\n            icon: const Icon(Icons.refresh),\n            tooltip: 'Azzera',\n          ),\n        ],\n      ),\n      body: Center(\n        child: Column(\n          mainAxisAlignment: MainAxisAlignment.center,\n          children: [\n            BadgeValore(\n              etichetta: 'Click',\n              valore: _contatore,\n              colore: raggiunto ? Colors.green : Colors.indigo,\n            ),\n            const SizedBox(height: 16),\n            Text(raggiunto ? 'Obiettivo raggiunto!' : 'Continua a premere'),\n          ],\n        ),\n      ),\n      floatingActionButton: Row(\n        mainAxisAlignment: MainAxisAlignment.end,\n        children: [\n          FloatingActionButton(\n            heroTag: 'meno',\n            onPressed: _contatore == 0 ? null : _decrementa,\n            child: const Icon(Icons.remove),\n          ),\n          const SizedBox(width: 12),\n          FloatingActionButton(\n            heroTag: 'piu',\n            onPressed: _incrementa,\n            child: const Icon(Icons.add),\n          ),\n        ],\n      ),\n    );\n  }\n}","Il numero a schermo cambia a ogni tocco, il badge diventa verde a quota 10 e i pulsanti si disabilitano quando il contatore è a zero.",{"id":64,"position":65,"title":66,"body":67,"code_snippet":68,"code_language":39,"expected_result":69,"demo_url":41,"video_url":41},518,5,"Far comunicare padre e figlio: dati giù, eventi su","Il figlio non deve conoscere lo stato del padre: riceve **dati** in ingresso e restituisce **eventi** tramite callback. È il pattern *data down, events up*, la base di ogni architettura Flutter.\n\nCreiamo un widget `PannelloContatore` **stateless** che riceve il valore e due funzioni. Tutta la logica resta nel `State` del padre, mentre il figlio si limita a disegnare e a notificare.\n\nQuesto ti permette di riutilizzare `PannelloContatore` ovunque e, soprattutto, rende chiaro dove risiede la \"verità\" dei dati: **una sola fonte**, nel widget con stato più vicino possibile ai widget che lo usano.","class PannelloContatore extends StatelessWidget {\n  const PannelloContatore({\n    super.key,\n    required this.valore,\n    required this.onIncrementa,\n    this.onDecrementa,\n  });\n\n  final int valore;\n  final VoidCallback onIncrementa;\n  final VoidCallback? onDecrementa; \u002F\u002F null = pulsante disabilitato\n\n  @override\n  Widget build(BuildContext context) {\n    return Card(\n      margin: const EdgeInsets.all(24),\n      child: Padding(\n        padding: const EdgeInsets.all(16),\n        child: Row(\n          mainAxisAlignment: MainAxisAlignment.spaceBetween,\n          children: [\n            IconButton.filledTonal(\n              onPressed: onDecrementa,\n              icon: const Icon(Icons.remove),\n            ),\n            Text('$valore', style: Theme.of(context).textTheme.headlineMedium),\n            IconButton.filled(\n              onPressed: onIncrementa,\n              icon: const Icon(Icons.add),\n            ),\n          ],\n        ),\n      ),\n    );\n  }\n}\n\n\u002F\u002F Nel build del padre (_HomePageState):\n\u002F\u002F PannelloContatore(\n\u002F\u002F   valore: _contatore,\n\u002F\u002F   onIncrementa: _incrementa,\n\u002F\u002F   onDecrementa: _contatore == 0 ? null : _decrementa,\n\u002F\u002F )","Il contatore funziona esattamente come prima, ma l'interfaccia è ora incapsulata in un widget stateless riutilizzabile.",{"id":71,"position":72,"title":73,"body":74,"code_snippet":75,"code_language":39,"expected_result":76,"demo_url":41,"video_url":41},519,6,"Ciclo di vita: initState, dispose e setState asincrono","Un `State` ha un ciclo di vita che devi conoscere per evitare gli errori più comuni:\n\n- `initState()` — chiamato **una sola volta** alla creazione: qui inizializzi controller, timer, listener. Non chiamare `setState` qui (il build non è ancora avvenuto) e non usare `context` per cose come `MediaQuery` senza cautela.\n- `build()` — può essere chiamato molte volte al secondo: nessuna logica pesante qui.\n- `dispose()` — chiamato alla rimozione del widget: qui **cancelli** timer, controller e stream, altrimenti hai un memory leak.\n\nL'errore classico è: `setState() called after dispose()`. Succede quando un'operazione asincrona termina dopo che l'utente ha già lasciato la schermata. La difesa è controllare `if (!mounted) return;` prima di chiamare `setState`.\n\nEcco un esempio completo con un timer automatico.","import 'dart:async';\n\nclass _HomePageState extends State\u003CHomePage> {\n  int _contatore = 0;\n  Timer? _timer;\n  bool _caricamento = false;\n\n  @override\n  void initState() {\n    super.initState();\n    \u002F\u002F Incrementa da solo ogni 3 secondi.\n    _timer = Timer.periodic(const Duration(seconds: 3), (_) {\n      setState(() => _contatore++);\n    });\n  }\n\n  Future\u003Cvoid> _caricaDalServer() async {\n    setState(() => _caricamento = true);\n\n    \u002F\u002F Simula una chiamata di rete.\n    await Future.delayed(const Duration(seconds: 2));\n    final valoreRemoto = 42;\n\n    \u002F\u002F Fondamentale: l'utente potrebbe essere uscito dalla schermata.\n    if (!mounted) return;\n\n    setState(() {\n      _contatore = valoreRemoto;\n      _caricamento = false;\n    });\n  }\n\n  @override\n  void dispose() {\n    _timer?.cancel(); \u002F\u002F sempre! altrimenti il timer continua a girare\n    super.dispose();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(title: const Text('Stato in Flutter')),\n      body: Center(\n        child: _caricamento\n            ? const CircularProgressIndicator()\n            : PannelloContatore(\n                valore: _contatore,\n                onIncrementa: () => setState(() => _contatore++),\n                onDecrementa:\n                    _contatore == 0 ? null : () => setState(() => _contatore--),\n              ),\n      ),\n      floatingActionButton: FloatingActionButton.extended(\n        onPressed: _caricamento ? null : _caricaDalServer,\n        icon: const Icon(Icons.cloud_download),\n        label: const Text('Carica'),\n      ),\n    );\n  }\n}","Il contatore avanza da solo ogni 3 secondi, il pulsante \"Carica\" mostra uno spinner per 2 secondi e poi imposta 42. Uscendo dalla schermata non compaiono errori in console.",{"id":78,"position":79,"title":80,"body":81,"code_snippet":82,"code_language":39,"expected_result":83,"demo_url":41,"video_url":41},520,7,"Errori tipici e regole da ricordare","Chiudiamo con una checklist mentale da tenere a portata di mano.\n\n**1. La UI non si aggiorna** → hai modificato la variabile senza `setState`, oppure la variabile è dichiarata **dentro** `build()` (in quel caso viene ricreata a ogni ricostruzione e riparte dal valore iniziale). Le variabili di stato vanno nei campi della classe `State`.\n\n**2. `setState() called during build`** → stai chiamando `setState` direttamente nel corpo di `build()`. Spostalo in una callback (`onPressed`, `onChanged`) o in `initState`\u002F`addPostFrameCallback`.\n\n**3. `setState() called after dispose()`** → aggiungi `if (!mounted) return;` dopo ogni `await`.\n\n**4. Logica pesante dentro setState** → esegui prima il calcolo, poi usa `setState` solo per assegnare il risultato.\n\n**5. Uso di StatefulWidget quando non serve** → se il widget non ha dati che cambiano nel tempo, usa `StatelessWidget`: è più leggero e più chiaro.\n\n**6. Rebuild troppo ampi** → `setState` ricostruisce **tutto** il `build()` di quel `State`. Estrai le parti statiche in widget `const` separati per limitare il lavoro.\n\nQuando lo stato deve essere condiviso tra schermate diverse, `setState` non basta più: è il momento di passare a soluzioni come Provider, Riverpod o BLoC. Ma il concetto resta identico: **cambio i dati, notifico Flutter, Flutter ridisegna**.","\u002F\u002F ❌ SBAGLIATO: la variabile riparte da 0 a ogni rebuild\n@override\nWidget build(BuildContext context) {\n  int contatore = 0; \u002F\u002F ricreata ogni volta!\n  return TextButton(\n    onPressed: () => setState(() => contatore++),\n    child: Text('$contatore'),\n  );\n}\n\n\u002F\u002F ✅ CORRETTO: la variabile è un campo del State\nclass _EsempioState extends State\u003CEsempio> {\n  int _contatore = 0;\n\n  @override\n  Widget build(BuildContext context) {\n    return TextButton(\n      onPressed: () => setState(() => _contatore++),\n      child: Text('$_contatore'),\n    );\n  }\n}\n\n\u002F\u002F ✅ Async fatto bene\nFuture\u003Cvoid> _carica() async {\n  final dati = await servizio.leggi(); \u002F\u002F fuori da setState\n  if (!mounted) return;\n  setState(() => _dati = dati);        \u002F\u002F dentro setState solo l'assegnazione\n}","Sai riconoscere e correggere i quattro errori più frequenti legati allo stato e scegliere consapevolmente tra StatelessWidget e StatefulWidget.",[],1789120599422]