[{"data":1,"prerenderedAt":28},["ShallowReactive",2],{"articolo-rendering-di-grandi-liste-in-flutter-ottimizzare-con-listviewbuilder-cacheextent-e-automatickeepalive":3,"comments-article-rendering-di-grandi-liste-in-flutter-ottimizzare-con-listviewbuilder-cacheextent-e-automatickeepalive":27},{"id":4,"title":5,"slug":6,"excerpt":7,"body":8,"cover_image":9,"cover_remote_url":10,"cover_credit":11,"video_url":15,"status":16,"published_at":17,"meta_title":18,"meta_description":19,"category":20,"author":24},59,"Rendering di grandi liste in Flutter: ottimizzare con ListView.builder, cacheExtent e AutomaticKeepAlive","rendering-di-grandi-liste-in-flutter-ottimizzare-con-listviewbuilder-cacheextent-e-automatickeepalive","Come costruire liste performanti in Flutter gestendo migliaia di elementi. Analizziamo ListView.builder, itemExtent, cacheExtent, keys e il mantenimento dello stato con AutomaticKeepAliveClientMixin.","## Perché le liste possono diventare un problema\n\nLe liste sono uno dei componenti più usati in qualsiasi app: feed social, elenchi di prodotti, chat, cronologie. Finché gli elementi sono pochi, qualsiasi approccio funziona. Ma quando dobbiamo mostrare centinaia o migliaia di righe, un uso ingenuo dei widget porta a scatti, consumo eccessivo di memoria e rebuild inutili.\n\nIn questo articolo vediamo come costruire liste realmente performanti sfruttando gli strumenti che Flutter mette a disposizione: il costruttore lazy, il controllo dell'area di cache, le key corrette e il mantenimento dello stato.\n\n## ListView vs ListView.builder\n\nIl primo errore comune è usare il costruttore di default di `ListView` passando una lista di `children`. In questo modo **tutti** i widget vengono costruiti subito, anche quelli fuori dallo schermo.\n\n```dart\n\u002F\u002F NON fare questo con liste lunghe\nListView(\n  children: items.map((item) => ItemTile(item: item)).toList(),\n)\n```\n\nLa soluzione corretta è `ListView.builder`, che costruisce gli elementi in modo pigro (lazy), solo quando stanno per entrare nel viewport:\n\n```dart\nListView.builder(\n  itemCount: items.length,\n  itemBuilder: (context, index) {\n    return ItemTile(item: items[index]);\n  },\n)\n```\n\nCon `ListView.builder` Flutter mantiene in memoria solo gli elementi visibili più un piccolo margine, riducendo drasticamente il costo di rendering.\n\n## itemExtent e prototypeItem: aiutare il layout\n\nQuando tutti gli elementi hanno la stessa altezza, possiamo comunicarlo a Flutter tramite `itemExtent`. Questo evita al framework di dover calcolare la dimensione di ogni figlio, migliorando le performance di scroll e permettendo salti diretti a una posizione.\n\n```dart\nListView.builder(\n  itemExtent: 72,\n  itemCount: items.length,\n  itemBuilder: (context, index) => ItemTile(item: items[index]),\n)\n```\n\nSe l'altezza è costante ma non la conosci a priori, puoi usare `prototypeItem`: Flutter misura quel widget campione una sola volta e applica la dimensione a tutti.\n\n```dart\nListView.builder(\n  prototypeItem: const ItemTile.placeholder(),\n  itemCount: items.length,\n  itemBuilder: (context, index) => ItemTile(item: items[index]),\n)\n```\n\n## cacheExtent: bilanciare fluidità e memoria\n\nIl parametro `cacheExtent` definisce quanti pixel oltre il viewport visibile Flutter deve pre-costruire. Un valore più alto rende lo scroll più fluido (gli elementi sono già pronti) ma consuma più memoria; un valore basso risparmia memoria ma può causare micro-scatti.\n\n```dart\nListView.builder(\n  cacheExtent: 500, \u002F\u002F pixel pre-renderizzati oltre lo schermo\n  itemCount: items.length,\n  itemBuilder: (context, index) => ItemTile(item: items[index]),\n)\n```\n\nUn buon punto di partenza è lasciare il default e regolarlo solo se noti problemi. Per liste con elementi pesanti (immagini, grafici) conviene tenerlo contenuto.\n\n## Le key: evitare rebuild sbagliati\n\nQuando la lista cambia (inserimenti, rimozioni, riordini), Flutter deve capire quali widget riutilizzare. Senza key corrette, il framework potromo associare lo stato al widget sbagliato, causando bug visivi.\n\nUsa una `ValueKey` basata su un identificatore stabile dell'elemento:\n\n```dart\nListView.builder(\n  itemCount: items.length,\n  itemBuilder: (context, index) {\n    final item = items[index];\n    return ItemTile(\n      key: ValueKey(item.id),\n      item: item,\n    );\n  },\n)\n```\n\nLe key sono particolarmente importanti quando gli elementi hanno uno stato interno (form, checkbox, animazioni) o quando la lista viene riordinata.\n\n## Mantenere lo stato con AutomaticKeepAliveClientMixin\n\nPer default, quando un elemento esce dal viewport il suo stato viene distrutto per liberare memoria. Questo è desiderabile nella maggior parte dei casi, ma a volte vogliamo preservare lo stato di un elemento costoso (ad esempio un video in pausa o un widget con animazione in corso).\n\nIn quei casi usiamo `AutomaticKeepAliveClientMixin`:\n\n```dart\nclass VideoTile extends StatefulWidget {\n  const VideoTile({super.key, required this.item});\n  final Item item;\n\n  @override\n  State\u003CVideoTile> createState() => _VideoTileState();\n}\n\nclass _VideoTileState extends State\u003CVideoTile>\n    with AutomaticKeepAliveClientMixin {\n  @override\n  bool get wantKeepAlive => true;\n\n  @override\n  Widget build(BuildContext context) {\n    super.build(context); \u002F\u002F obbligatorio con il mixin\n    return \u002F* il tuo widget con stato da preservare *\u002F;\n  }\n}\n```\n\nAttenzione: usa `wantKeepAlive` con parsimonia. Mantenere vivi molti elementi vanifica i benefici della lazy list e aumenta il consumo di memoria.\n\n## RepaintBoundary per elementi complessi\n\nSe ogni riga contiene grafica costosa da ridisegnare, avvolgerla in un `RepaintBoundary` isola il suo layer di pittura, evitando che un ridisegno si propaghi agli elementi vicini.\n\n```dart\nitemBuilder: (context, index) {\n  return RepaintBoundary(\n    child: ComplexTile(item: items[index]),\n  );\n}\n```\n\nNon abusarne: ogni boundary ha un costo. Usalo solo quando profili mostrano problemi di repaint.\n\n## ListView.separated per i divisori\n\nSe hai bisogno di separatori tra gli elementi, `ListView.separated` è più pulito ed efficiente rispetto a inserire i divisori manualmente:\n\n```dart\nListView.separated(\n  itemCount: items.length,\n  itemBuilder: (context, index) => ItemTile(item: items[index]),\n  separatorBuilder: (context, index) => const Divider(height: 1),\n)\n```\n\n## Checklist per liste performanti\n\n- Usa sempre `ListView.builder` (o `.separated`) per liste lunghe.\n- Imposta `itemExtent` o `prototypeItem` se l'altezza è costante.\n- Assegna `ValueKey` stabili quando gli elementi hanno stato o vengono riordinati.\n- Usa `AutomaticKeepAliveClientMixin` solo per stati costosi da ricreare.\n- Considera `RepaintBoundary` per righe graficamente pesanti.\n- Profila con DevTools prima di ottimizzare: misura, non indovinare.\n\n## Conclusione\n\nLe liste performanti in Flutter non richiedono magia, ma la conoscenza degli strumenti giusti. Partendo da `ListView.builder` e affinando con `itemExtent`, `cacheExtent`, key corrette e mantenimento selettivo dello stato, puoi gestire migliaia di elementi mantenendo lo scroll fluido a 60 (o 120) fps. La regola d'oro resta sempre la stessa: misura con Flutter DevTools e ottimizza solo dove serve davvero.","https:\u002F\u002Fflutter.it\u002Fstorage\u002Farticles\u002F1695f5ca-b56c-43ab-99d6-629702841418.jpg","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1553448410-3b3b2584498c?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzA2NTJ8MHwxfHJhbmRvbXx8fHx8fHx8fDE3ODczMTIxMDF8&ixlib=rb-4.1.0&q=80&w=1080",{"name":12,"author_url":13,"photo_url":14},"Christian Wiediger","https:\u002F\u002Funsplash.com\u002F@christianw","https:\u002F\u002Funsplash.com\u002Fphotos\u002Fassorted-books-on-bookshelves-i2cwRt3WxZk",null,"published","2026-08-07T04:00:44+00:00","Liste performanti in Flutter: ListView.builder e cacheExtent","Guida pratica per rendere performanti le liste in Flutter: ListView.builder, itemExtent, cacheExtent, key corrette e AutomaticKeepAliveClientMixin.",{"id":21,"name":22,"slug":23},3,"Best practice","best-practice",{"id":25,"name":26},1,"Flutter Bot",[],1789120588927]