[{"data":1,"prerenderedAt":81},["ShallowReactive",2],{"tutorial-gestire-lo-stato-locale-in-flutter-con-signals-e-reattivita-granulare":3,"comments-tutorial-gestire-lo-stato-locale-in-flutter-con-signals-e-reattivita-granulare":80},{"id":4,"title":5,"slug":6,"excerpt":7,"intro":8,"cover_image":9,"cover_remote_url":10,"cover_credit":11,"video_url":15,"video":15,"difficulty":16,"estimated_minutes":17,"flutter_version":18,"status":19,"published_at":20,"meta_title":21,"meta_description":22,"category":23,"author":27,"steps":29},67,"Gestire lo stato locale in Flutter con signals e reattività granulare","gestire-lo-stato-locale-in-flutter-con-signals-e-reattivita-granulare","Scopri come usare il pacchetto signals per gestire lo stato in Flutter con reattività granulare, senza boilerplate e con ricostruzioni mirate dei widget.","# Gestione dello stato con signals\n\nIl pacchetto [`signals`](https:\u002F\u002Fpub.dev\u002Fpackages\u002Fsignals) porta in Flutter un modello di reattività *fine-grained* (granulare) ispirato a SolidJS e Preact. A differenza di soluzioni come Provider o Riverpod, con i signals ogni valore reattivo sa esattamente quali widget dipendono da esso: quando cambia, solo quei widget vengono ricostruiti, senza `notifyListeners` manuali né boilerplate.\n\nIn questo tutorial costruiremo un piccolo carrello della spesa reattivo, imparando a usare `signal`, `computed`, `effect` e il widget `Watch` per aggiornare la UI in modo mirato ed efficiente.","https:\u002F\u002Fflutter.it\u002Fstorage\u002Ftutorials\u002F19ec8ea8-b107-455a-beff-31f860f10f18.jpg","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1527904188605-3424bcc2d107?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzA2NTJ8MHwxfHJhbmRvbXx8fHx8fHx8fDE3ODczMjczMjR8&ixlib=rb-4.1.0&q=80&w=1080",{"name":12,"author_url":13,"photo_url":14},"Anna Sullivan","https:\u002F\u002Funsplash.com\u002F@aesullivan2010","https:\u002F\u002Funsplash.com\u002Fphotos\u002Fclear-body-of-watter-iUmoREuqGbo",null,"intermediate",35,"3.x","published","2026-08-18T04:30:58+00:00","Gestione stato in Flutter con signals: guida pratica","Impara a gestire lo stato in Flutter con il pacchetto signals: reattività granulare, computed, effect e il widget Watch senza boilerplate.",{"id":24,"name":25,"slug":26},1,"Guide","guide",{"id":24,"name":28},"Flutter Bot",[30,37,45,52,59,66,73],{"id":31,"position":24,"title":32,"body":33,"code_snippet":34,"code_language":35,"expected_result":36,"demo_url":15,"video_url":15},446,"Installare il pacchetto signals","Aggiungiamo il pacchetto `signals` al progetto. Puoi usare il comando `flutter pub add` oppure inserire la dipendenza manualmente nel file `pubspec.yaml`.\n\nIl pacchetto è composto da un core Dart e da un'integrazione Flutter (widget come `Watch` e `SignalProvider`), entrambi esposti dall'import `package:signals\u002Fsignals_flutter.dart`.","flutter pub add signals","bash","Nel pubspec.yaml compare la dipendenza signals e `flutter pub get` termina senza errori.",{"id":38,"position":39,"title":40,"body":41,"code_snippet":42,"code_language":43,"expected_result":44,"demo_url":15,"video_url":15},447,2,"Creare il primo signal","Un `signal` è un contenitore reattivo per un valore. Si legge con `.value` e si aggiorna assegnando un nuovo valore a `.value`.\n\nCreiamo un semplice contatore. Il concetto chiave è che chiunque legga `counter.value` all'interno di un contesto reattivo verrà automaticamente notificato quando il valore cambia.","import 'package:signals\u002Fsignals_flutter.dart';\n\n\u002F\u002F Un signal che contiene un intero\nfinal counter = signal(0);\n\nvoid incrementa() {\n  counter.value++; \u002F\u002F aggiorna il valore e notifica i dipendenti\n}\n\nint leggi() {\n  return counter.value; \u002F\u002F legge il valore corrente\n}","dart","Hai definito un signal `counter` che potrà essere osservato e modificato.",{"id":46,"position":47,"title":48,"body":49,"code_snippet":50,"code_language":43,"expected_result":51,"demo_url":15,"video_url":15},448,3,"Reagire ai cambiamenti nella UI con Watch","Il widget `Watch` ricostruisce solo il suo contenuto quando cambiano i signals letti al suo interno. Questo evita di ricostruire l'intero widget tree.\n\nAvvolgi con `Watch` esclusivamente la porzione di UI che dipende dal signal: più il `Watch` è piccolo e mirato, più le ricostruzioni saranno efficienti.","import 'package:flutter\u002Fmaterial.dart';\nimport 'package:signals\u002Fsignals_flutter.dart';\n\nfinal counter = signal(0);\n\nclass ContatoreView extends StatelessWidget {\n  const ContatoreView({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(title: const Text('Signals demo')),\n      body: Center(\n        \u002F\u002F Solo questo testo si ricostruisce quando counter cambia\n        child: Watch((context) => Text(\n          'Valore: ${counter.value}',\n          style: Theme.of(context).textTheme.headlineMedium,\n        )),\n      ),\n      floatingActionButton: FloatingActionButton(\n        onPressed: () => counter.value++,\n        child: const Icon(Icons.add),\n      ),\n    );\n  }\n}","Premendo il FAB il testo si aggiorna, ma il resto dello Scaffold non viene ricostruito.",{"id":53,"position":54,"title":55,"body":56,"code_snippet":57,"code_language":43,"expected_result":58,"demo_url":15,"video_url":15},449,4,"Valori derivati con computed","Un `computed` è un signal di sola lettura calcolato a partire da altri signals. Si aggiorna automaticamente quando cambiano le sue dipendenze e memorizza il risultato (memoization) finché non serve ricalcolarlo.\n\nModelliamo un carrello: una lista di prodotti come signal, e il totale come `computed` che dipende dalla lista.","import 'package:signals\u002Fsignals_flutter.dart';\n\nclass Prodotto {\n  final String nome;\n  final double prezzo;\n  final int quantita;\n  const Prodotto(this.nome, this.prezzo, this.quantita);\n}\n\n\u002F\u002F Stato principale: lista di prodotti nel carrello\nfinal carrello = signal(\u003CProdotto>[]);\n\n\u002F\u002F Valore derivato: totale del carrello\nfinal totale = computed(() => carrello.value.fold\u003Cdouble>(\n      0,\n      (somma, p) => somma + p.prezzo * p.quantita,\n    ));\n\n\u002F\u002F Valore derivato: numero totale di articoli\nfinal numeroArticoli = computed(\n  () => carrello.value.fold\u003Cint>(0, (s, p) => s + p.quantita),\n);\n\nvoid aggiungiProdotto(Prodotto p) {\n  \u002F\u002F Creiamo una nuova lista per far scattare la notifica\n  carrello.value = [...carrello.value, p];\n}","Aggiungendo prodotti al carrello, `totale` e `numeroArticoli` si ricalcolano automaticamente.",{"id":60,"position":61,"title":62,"body":63,"code_snippet":64,"code_language":43,"expected_result":65,"demo_url":15,"video_url":15},450,5,"Eseguire side-effect con effect","Un `effect` esegue una funzione ogni volta che cambia uno dei signals letti al suo interno. È utile per side-effect: logging, salvataggio su disco, chiamate di rete.\n\n`effect` restituisce una funzione di dispose da chiamare quando non serve più (ad esempio nel `dispose` di uno State) per evitare memory leak.","import 'package:flutter\u002Ffoundation.dart';\nimport 'package:signals\u002Fsignals_flutter.dart';\n\nlate final void Function() disposeLogger;\n\nvoid avviaLogger() {\n  disposeLogger = effect(() {\n    \u002F\u002F Viene rieseguito ad ogni cambiamento di totale o numeroArticoli\n    debugPrint('Carrello: ${numeroArticoli.value} articoli, '\n        'totale €${totale.value.toStringAsFixed(2)}');\n  });\n}\n\n\u002F\u002F Quando non serve più:\n\u002F\u002F disposeLogger();","Ogni modifica al carrello stampa in console il riepilogo aggiornato del totale.",{"id":67,"position":68,"title":69,"body":70,"code_snippet":71,"code_language":43,"expected_result":72,"demo_url":15,"video_url":15},451,6,"Comporre lo stato in una schermata completa","Mettiamo insieme i concetti in una schermata carrello. Nota come usiamo `Watch` separati: uno per la lista e uno per il riepilogo del totale, così le due parti si ricostruiscono in modo indipendente.\n\nQuesta separazione è il grande vantaggio della reattività granulare: aggiungere un prodotto ricostruisce solo la lista e il footer, non tutta la schermata.","class CarrelloScreen extends StatelessWidget {\n  const CarrelloScreen({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(title: const Text('Carrello')),\n      body: Column(\n        children: [\n          Expanded(\n            child: Watch((context) {\n              final prodotti = carrello.value;\n              if (prodotti.isEmpty) {\n                return const Center(child: Text('Carrello vuoto'));\n              }\n              return ListView.builder(\n                itemCount: prodotti.length,\n                itemBuilder: (context, i) {\n                  final p = prodotti[i];\n                  return ListTile(\n                    title: Text(p.nome),\n                    subtitle: Text('x${p.quantita}'),\n                    trailing: Text('€${(p.prezzo * p.quantita)\n                        .toStringAsFixed(2)}'),\n                  );\n                },\n              );\n            }),\n          ),\n          \u002F\u002F Footer che dipende solo dai computed\n          Watch((context) => Container(\n            padding: const EdgeInsets.all(16),\n            color: Theme.of(context).colorScheme.surfaceVariant,\n            child: Row(\n              mainAxisAlignment: MainAxisAlignment.spaceBetween,\n              children: [\n                Text('${numeroArticoli.value} articoli'),\n                Text('Totale: €${totale.value.toStringAsFixed(2)}',\n                    style: const TextStyle(fontWeight: FontWeight.bold)),\n              ],\n            ),\n          )),\n        ],\n      ),\n      floatingActionButton: FloatingActionButton.extended(\n        onPressed: () => aggiungiProdotto(\n          const Prodotto('Caffè', 4.50, 1),\n        ),\n        label: const Text('Aggiungi'),\n        icon: const Icon(Icons.add_shopping_cart),\n      ),\n    );\n  }\n}","La schermata mostra la lista dei prodotti e un footer con totale e conteggio che si aggiornano automaticamente ad ogni aggiunta.",{"id":74,"position":75,"title":76,"body":77,"code_snippet":78,"code_language":43,"expected_result":79,"demo_url":15,"video_url":15},452,7,"Buone pratiche e gestione del ciclo di vita","Alcune regole per usare i signals in modo efficace:\n\n- **Watch mirati**: avvolgi con `Watch` solo la parte di UI che dipende dal signal, non interi widget.\n- **Immutabilità delle collezioni**: per liste e mappe assegna sempre una *nuova* istanza (`[...lista]`) così il signal rileva il cambiamento.\n- **Dispose degli effect**: se crei `effect` o signal legati a uno State, ricordati di rilasciarli.\n- **Signal locali con hook**: in uno `StatefulWidget` puoi usare `createSignal` \u002F `bindSignal` per legare il ciclo di vita del signal al widget.\n\nEsempio di signal locale con dispose automatico dentro uno State:","class LocalCounter extends StatefulWidget {\n  const LocalCounter({super.key});\n  @override\n  State\u003CLocalCounter> createState() => _LocalCounterState();\n}\n\nclass _LocalCounterState extends State\u003CLocalCounter> {\n  \u002F\u002F Signal legato al ciclo di vita del widget\n  late final count = createSignal(this, 0);\n\n  @override\n  Widget build(BuildContext context) {\n    return TextButton(\n      onPressed: () => count.value++,\n      child: Watch((context) => Text('Cliccato ${count.value} volte')),\n    );\n  }\n}","Il signal locale viene creato e disposto insieme al widget, evitando memory leak, e il bottone aggiorna solo il proprio testo.",[],1789120601294]