[{"data":1,"prerenderedAt":74},["ShallowReactive",2],{"tutorial-gestire-le-richieste-asincrone-in-flutter-con-futurebuilder-e-streambuilder":3,"comments-tutorial-gestire-le-richieste-asincrone-in-flutter-con-futurebuilder-e-streambuilder":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":24},12,"Gestire le richieste asincrone in Flutter con FutureBuilder e StreamBuilder","gestire-le-richieste-asincrone-in-flutter-con-futurebuilder-e-streambuilder","Impara a costruire interfacce reattive che reagiscono ai dati asincroni usando FutureBuilder e StreamBuilder, gestendo stati di caricamento, errore e successo.","## Introduzione\n\nIn quasi ogni app reale dovrai mostrare dati che arrivano in modo **asincrono**: una risposta da un server, una lettura da database, un timer o un flusso continuo di eventi. Flutter offre due widget potentissimi per collegare questi dati alla UI in modo dichiarativo: `FutureBuilder` (per un singolo risultato futuro) e `StreamBuilder` (per una sequenza di valori nel tempo).\n\nIn questo tutorial costruiremo esempi pratici che mostrano come gestire correttamente gli stati di **caricamento**, **errore** e **dati pronti**, evitando gli errori più comuni come la ricreazione del Future ad ogni rebuild.\n\nAl termine saprai scegliere lo strumento giusto in base al tuo caso d'uso e scrivere UI robuste e pulite.","https:\u002F\u002Fflutter.it\u002Fstorage\u002Ftutorials\u002Ff17062db-e1b5-46e6-a694-f30423f60333.jpg",null,"intermediate",35,"3.x","published","2026-06-17T04:30:48+00:00","FutureBuilder e StreamBuilder in Flutter: guida pratica","Gestisci dati asincroni in Flutter con FutureBuilder e StreamBuilder: stati di caricamento, errore e successo con esempi pratici.",{"id":19,"name":20,"slug":21},1,"Guide","guide",{"id":19,"name":23},"Flutter Bot",[25,32,39,46,53,60,67],{"id":26,"position":19,"title":27,"body":28,"code_snippet":29,"code_language":30,"expected_result":31,"demo_url":10,"video_url":10},83,"Capire la differenza tra Future e Stream","Prima di scrivere widget, è fondamentale capire i due concetti:\n\n- Un **`Future`** rappresenta un valore che sarà disponibile **una sola volta** in futuro (es. il risultato di una chiamata HTTP).\n- Uno **`Stream`** rappresenta una **sequenza di valori** emessi nel tempo (es. aggiornamenti di posizione, ticker, eventi WebSocket).\n\nDi conseguenza useremo `FutureBuilder` per un risultato singolo e `StreamBuilder` per dati continui.","\u002F\u002F Esempio di Future: un singolo valore dopo 2 secondi\nFuture\u003CString> caricaMessaggio() async {\n  await Future.delayed(const Duration(seconds: 2));\n  return 'Dati caricati con successo!';\n}\n\n\u002F\u002F Esempio di Stream: un valore al secondo\nStream\u003Cint> contatore() async* {\n  int i = 0;\n  while (true) {\n    await Future.delayed(const Duration(seconds: 1));\n    yield i++;\n  }\n}","dart","Hai definito una funzione che restituisce un Future e una che restituisce uno Stream, pronte da collegare alla UI.",{"id":33,"position":34,"title":35,"body":36,"code_snippet":37,"code_language":30,"expected_result":38,"demo_url":10,"video_url":10},84,2,"Usare FutureBuilder per un singolo risultato","`FutureBuilder` ricostruisce la UI ogni volta che cambia lo stato del `Future`. La proprietà `snapshot.connectionState` ci dice se siamo in attesa, mentre `snapshot.hasError` e `snapshot.hasData` ci dicono l'esito.\n\nGestiamo **sempre** tutti e tre i casi: caricamento, errore e dati pronti.","FutureBuilder\u003CString>(\n  future: caricaMessaggio(),\n  builder: (context, snapshot) {\n    if (snapshot.connectionState == ConnectionState.waiting) {\n      return const Center(child: CircularProgressIndicator());\n    }\n    if (snapshot.hasError) {\n      return Center(child: Text('Errore: ${snapshot.error}'));\n    }\n    return Center(\n      child: Text(\n        snapshot.data ?? 'Nessun dato',\n        style: const TextStyle(fontSize: 20),\n      ),\n    );\n  },\n)","Vedrai uno spinner per 2 secondi, poi il testo 'Dati caricati con successo!'.",{"id":40,"position":41,"title":42,"body":43,"code_snippet":44,"code_language":30,"expected_result":45,"demo_url":10,"video_url":10},85,3,"Evitare l'errore più comune: il Future ricreato ad ogni rebuild","Un errore frequentissimo è passare `future: caricaMessaggio()` **direttamente** nel `build`. Ogni volta che il widget viene ricostruito (es. per un `setState` o una rotazione), il `Future` viene rieseguito, mostrando di nuovo lo spinner.\n\nLa soluzione corretta è creare il `Future` **una sola volta** in un `StatefulWidget`, ad esempio in `initState`.","class MessaggioPage extends StatefulWidget {\n  const MessaggioPage({super.key});\n  @override\n  State\u003CMessaggioPage> createState() => _MessaggioPageState();\n}\n\nclass _MessaggioPageState extends State\u003CMessaggioPage> {\n  late final Future\u003CString> _futureMessaggio;\n\n  @override\n  void initState() {\n    super.initState();\n    _futureMessaggio = caricaMessaggio(); \u002F\u002F creato una sola volta\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return FutureBuilder\u003CString>(\n      future: _futureMessaggio,\n      builder: (context, snapshot) {\n        if (snapshot.connectionState == ConnectionState.waiting) {\n          return const Center(child: CircularProgressIndicator());\n        }\n        if (snapshot.hasError) {\n          return Center(child: Text('Errore: ${snapshot.error}'));\n        }\n        return Center(child: Text(snapshot.data ?? ''));\n      },\n    );\n  }\n}","Anche dopo rebuild o rotazioni, il Future non viene rieseguito e lo spinner non riappare inutilmente.",{"id":47,"position":48,"title":49,"body":50,"code_snippet":51,"code_language":30,"expected_result":52,"demo_url":10,"video_url":10},86,4,"Usare StreamBuilder per dati continui","Per un flusso di valori usiamo `StreamBuilder`. La struttura è simile a `FutureBuilder`, ma `snapshot.data` viene aggiornato ad ogni nuovo evento emesso dallo Stream.\n\nAnche qui conviene creare lo Stream una sola volta (in `initState` o come campo) per evitare che venga riavviato ad ogni rebuild.","class ContatorePage extends StatefulWidget {\n  const ContatorePage({super.key});\n  @override\n  State\u003CContatorePage> createState() => _ContatorePageState();\n}\n\nclass _ContatorePageState extends State\u003CContatorePage> {\n  late final Stream\u003Cint> _stream;\n\n  @override\n  void initState() {\n    super.initState();\n    _stream = contatore();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return StreamBuilder\u003Cint>(\n      stream: _stream,\n      initialData: 0,\n      builder: (context, snapshot) {\n        if (snapshot.hasError) {\n          return Center(child: Text('Errore: ${snapshot.error}'));\n        }\n        return Center(\n          child: Text(\n            'Valore: ${snapshot.data}',\n            style: const TextStyle(fontSize: 32),\n          ),\n        );\n      },\n    );\n  }\n}","Il numero a schermo si incrementa automaticamente ogni secondo.",{"id":54,"position":55,"title":56,"body":57,"code_snippet":58,"code_language":30,"expected_result":59,"demo_url":10,"video_url":10},87,5,"Gestire le risorse: chiudere gli StreamController","Quando crei manualmente uno `StreamController`, devi **chiuderlo** quando il widget viene rimosso, per evitare memory leak. Questo si fa nel metodo `dispose`.\n\nVediamo un esempio in cui controlliamo noi lo Stream con uno `StreamController`.","class TimerManualePage extends StatefulWidget {\n  const TimerManualePage({super.key});\n  @override\n  State\u003CTimerManualePage> createState() => _TimerManualePageState();\n}\n\nclass _TimerManualePageState extends State\u003CTimerManualePage> {\n  final _controller = StreamController\u003Cint>();\n  Timer? _timer;\n  int _secondi = 0;\n\n  @override\n  void initState() {\n    super.initState();\n    _timer = Timer.periodic(const Duration(seconds: 1), (_) {\n      _secondi++;\n      _controller.add(_secondi);\n    });\n  }\n\n  @override\n  void dispose() {\n    _timer?.cancel();\n    _controller.close(); \u002F\u002F fondamentale per evitare leak\n    super.dispose();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return StreamBuilder\u003Cint>(\n      stream: _controller.stream,\n      initialData: 0,\n      builder: (context, snapshot) =>\n          Center(child: Text('Secondi: ${snapshot.data}')),\n    );\n  }\n}","Il timer funziona correttamente e le risorse vengono liberate quando si esce dalla pagina.",{"id":61,"position":62,"title":63,"body":64,"code_snippet":65,"code_language":30,"expected_result":66,"demo_url":10,"video_url":10},88,6,"Aggiungere un pulsante di ricarica con FutureBuilder","Spesso vuoi permettere all'utente di **riprovare** dopo un errore o aggiornare i dati. Possiamo farlo riassegnando il `Future` dentro un `setState`. Poiché lo riassegniamo solo quando l'utente preme il pulsante, evitiamo ricariche indesiderate.","ElevatedButton(\n  onPressed: () {\n    setState(() {\n      _futureMessaggio = caricaMessaggio(); \u002F\u002F forza il ricaricamento\n    });\n  },\n  child: const Text('Ricarica'),\n)\n\n\u002F\u002F Inseriscilo accanto al FutureBuilder dentro una Column nel build()","Premendo 'Ricarica' lo spinner riappare e i dati vengono caricati nuovamente, in modo controllato.",{"id":68,"position":69,"title":70,"body":71,"code_snippet":10,"code_language":30,"expected_result":72,"demo_url":10,"video_url":10},89,7,"Quando usare cosa: riepilogo e best practice","Per chiudere, ecco una sintesi pratica:\n\n- **FutureBuilder**: usa per operazioni che restituiscono **un solo valore** (chiamata API, lettura DB singola). Crea il Future fuori dal `build`.\n- **StreamBuilder**: usa per **flussi continui** (real-time, timer, posizione GPS, WebSocket).\n- Gestisci **sempre** i tre stati: `waiting`, `hasError`, dati pronti.\n- Per gli Stream creati manualmente, **chiudi** sempre lo `StreamController` in `dispose`.\n- Per logiche di stato più complesse, considera soluzioni come `Provider`, `Riverpod` o `Bloc`, che usano questi widget internamente.\n\nCon queste basi puoi costruire UI reattive e robuste in qualsiasi app Flutter.","Hai una visione chiara di quale widget usare e delle best practice per gestire dati asincroni in Flutter.",[],1781680690580]