[{"data":1,"prerenderedAt":23},["ShallowReactive",2],{"articolo-rendering-condizionale-e-lazy-loading-di-widget-in-flutter-con-visibility-offstage-e-indexedstack":3,"comments-article-rendering-condizionale-e-lazy-loading-di-widget-in-flutter-con-visibility-offstage-e-indexedstack":22},{"id":4,"title":5,"slug":6,"excerpt":7,"body":8,"cover_image":9,"video_url":10,"status":11,"published_at":12,"meta_title":13,"meta_description":14,"category":15,"author":19},53,"Rendering condizionale e lazy loading di widget in Flutter con Visibility, Offstage e IndexedStack","rendering-condizionale-e-lazy-loading-di-widget-in-flutter-con-visibility-offstage-e-indexedstack","Mostrare e nascondere widget in Flutter sembra banale, ma ci sono differenze cruciali tra Visibility, Offstage, IndexedStack e la semplice rimozione dall'albero. Scopri quale usare e quando per non sprecare risorse.","## Introduzione\n\nUno degli errori più comuni tra chi inizia con Flutter è pensare che nascondere un widget sia una singola operazione. In realtà esistono numerose strategie, ognuna con implicazioni diverse su performance, stato e ricostruzioni. Scegliere quella sbagliata può portare a rebuild inutili, perdita dello stato di scroll o form, oppure a mantenere in memoria widget pesanti che non servono.\n\nIn questo articolo vediamo le tecniche principali per il rendering condizionale in Flutter: dalla semplice rimozione dall'albero, a `Visibility`, `Offstage`, fino a `IndexedStack` e al lazy loading vero e proprio.\n\n## 1. Rimozione dall'albero: il condizionale puro\n\nLa forma più diretta è includere o meno il widget nell'albero tramite un `if` inline o un operatore ternario:\n\n```dart\nColumn(\n  children: [\n    const Text('Sempre visibile'),\n    if (mostraDettagli) const DettagliWidget(),\n  ],\n)\n```\n\nQuando `mostraDettagli` è `false`, il widget **non esiste affatto**: non occupa spazio, non consuma memoria e non viene renderizzato. È l'approccio più efficiente in termini di risorse.\n\nLo svantaggio? Ogni volta che il widget torna visibile viene **ricreato da zero**, perdendo tutto lo stato interno (posizione di scroll, testo digitato, animazioni in corso).\n\n## 2. Il widget Visibility\n\n`Visibility` mantiene il widget nell'albero ma ne controlla la visibilità:\n\n```dart\nVisibility(\n  visible: mostraDettagli,\n  child: const DettagliWidget(),\n)\n```\n\nPer impostazione predefinita, quando `visible` è `false` il widget viene rimosso dal layout (come `Offstage`) e non occupa spazio. Possiamo però personalizzarne il comportamento:\n\n```dart\nVisibility(\n  visible: mostraDettagli,\n  maintainState: true,      \u002F\u002F mantiene lo State del widget\n  maintainAnimation: true,  \u002F\u002F continua le animazioni\n  maintainSize: true,       \u002F\u002F occupa comunque lo spazio\n  replacement: const SizedBox.shrink(),\n  child: const DettagliWidget(),\n)\n```\n\nL'opzione `maintainState: true` è utile quando vogliamo preservare lo stato interno (ad esempio un `TextField` compilato) senza distruggerlo. Attenzione: mantenere lo stato ha un costo, perché il widget resta vivo in memoria.\n\n## 3. Offstage: renderizzare senza mostrare\n\n`Offstage` posiziona il widget \"fuori dallo schermo\": il widget viene comunque costruito e ne viene calcolato il layout, ma non è dipinto né interattivo.\n\n```dart\nOffstage(\n  offstage: !mostraDettagli,\n  child: const DettagliWidget(),\n)\n```\n\nÈ utile quando serve conoscere le dimensioni di un widget prima di mostrarlo, o quando si vuole precaricarne il layout. Da usare con parsimonia: il layout viene comunque calcolato, quindi non è gratis.\n\n## 4. IndexedStack: gestire più \"pagine\" mantenendo lo stato\n\n`IndexedStack` mostra un solo figlio alla volta in base a un indice, mantenendo però vivi tutti gli altri. È l'ideale per le tab di una `BottomNavigationBar` quando vogliamo preservare lo stato di ogni schermata.\n\n```dart\nclass HomePage extends StatefulWidget {\n  const HomePage({super.key});\n\n  @override\n  State\u003CHomePage> createState() => _HomePageState();\n}\n\nclass _HomePageState extends State\u003CHomePage> {\n  int _indice = 0;\n\n  final _pagine = const [\n    FeedPage(),\n    RicercaPage(),\n    ProfiloPage(),\n  ];\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      body: IndexedStack(\n        index: _indice,\n        children: _pagine,\n      ),\n      bottomNavigationBar: NavigationBar(\n        selectedIndex: _indice,\n        onDestinationSelected: (i) => setState(() => _indice = i),\n        destinations: const [\n          NavigationDestination(icon: Icon(Icons.home), label: 'Feed'),\n          NavigationDestination(icon: Icon(Icons.search), label: 'Cerca'),\n          NavigationDestination(icon: Icon(Icons.person), label: 'Profilo'),\n        ],\n      ),\n    );\n  }\n}\n```\n\nCon `IndexedStack` la posizione di scroll del feed rimane intatta quando si passa alla ricerca e si torna indietro. Il rovescio della medaglia: **tutte le pagine vengono costruite subito**, anche quelle mai visitate, con un possibile impatto sull'avvio.\n\n## 5. Lazy loading: costruire solo al bisogno\n\nSe le pagine sono pesanti, possiamo combinare le tecniche per un caricamento pigro. Un pattern efficace è tenere traccia delle pagine già visitate e costruire solo quelle:\n\n```dart\nclass LazyIndexedStack extends StatefulWidget {\n  final int index;\n  final List\u003CWidget> children;\n\n  const LazyIndexedStack({\n    super.key,\n    required this.index,\n    required this.children,\n  });\n\n  @override\n  State\u003CLazyIndexedStack> createState() => _LazyIndexedStackState();\n}\n\nclass _LazyIndexedStackState extends State\u003CLazyIndexedStack> {\n  late final List\u003Cbool> _attivate =\n      List.generate(widget.children.length, (i) => i == widget.index);\n\n  @override\n  void didUpdateWidget(LazyIndexedStack oldWidget) {\n    super.didUpdateWidget(oldWidget);\n    _attivate[widget.index] = true;\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return IndexedStack(\n      index: widget.index,\n      children: List.generate(widget.children.length, (i) {\n        return _attivate[i]\n            ? widget.children[i]\n            : const SizedBox.shrink();\n      }),\n    );\n  }\n}\n```\n\nIn questo modo ogni pagina viene costruita solo la prima volta che viene selezionata, ma una volta attivata resta viva nell'albero e ne conserva lo stato. È il compromesso migliore tra avvio rapido e persistenza dello stato.\n\n## Tabella riassuntiva\n\n- **`if` inline**: massima efficienza, ma perde lo stato ad ogni riapparizione.\n- **`Visibility`**: flessibile, con `maintainState` per preservare lo stato.\n- **`Offstage`**: costruisce e calcola il layout ma non dipinge; utile per misurare.\n- **`IndexedStack`**: costruisce tutti i figli subito, mantiene sempre lo stato.\n- **`LazyIndexedStack`**: costruzione pigra + stato persistente.\n\n## Quando usare cosa\n\n- Per elementi UI leggeri che compaiono e scompaiono spesso e non hanno stato importante, usa l'`if` inline.\n- Per form o widget con stato che devono restare in memoria, opta per `Visibility(maintainState: true)`.\n- Per navigazione a tab dove la persistenza dello scroll è fondamentale, `IndexedStack` (o la versione lazy se le pagine sono pesanti).\n- Evita di lasciare in memoria widget molto pesanti (mappe, video, liste enormi) quando non servono: valuta sempre il trade-off tra reattività e consumo di risorse.\n\n## Conclusione\n\nIl rendering condizionale in Flutter non è mai una scelta banale. Comprendere la differenza tra rimuovere un widget dall'albero e semplicemente nasconderlo permette di scrivere UI più performanti e con uno stato coerente. La regola d'oro: nascondi solo ciò che vale la pena mantenere vivo, e rimuovi tutto il resto.","https:\u002F\u002Fflutter.it\u002Fstorage\u002Farticles\u002Fa5ee1302-b009-4b8e-bf50-1f7ec34eb6b4.jpg",null,"published","2026-07-27T04:00:48+00:00","Rendering condizionale in Flutter: Visibility e IndexedStack","Guida pratica al rendering condizionale in Flutter: differenze tra if inline, Visibility, Offstage, IndexedStack e lazy loading per UI performanti.",{"id":16,"name":17,"slug":18},3,"Best practice","best-practice",{"id":20,"name":21},1,"Flutter Bot",[],1785219595761]