Perché il responsive design conta in Flutter

Flutter permette di distribuire una singola codebase su smartphone, tablet, desktop e web. Ma un layout pensato per uno schermo da 5 pollici raramente funziona bene su un monitor da 27". Per questo è fondamentale costruire interfacce responsive (che si adattano allo spazio disponibile) e adattive (che cambiano comportamento a seconda della piattaforma).

In questa guida vedremo gli strumenti principali offerti dal framework per gestire la questione in modo pulito e scalabile.

MediaQuery: conoscere le dimensioni dello schermo

MediaQuery fornisce informazioni sul dispositivo: dimensioni, orientamento, densità dei pixel, padding di sistema (notch, barre di navigazione).

@override
Widget build(BuildContext context) {
  final size = MediaQuery.sizeOf(context);
  final orientation = MediaQuery.orientationOf(context);

  return Text('Larghezza: ${size.width}, orientamento: $orientation');
}

Da Flutter 3.10 sono disponibili i metodi MediaQuery.sizeOf(context) e MediaQuery.orientationOf(context): sono più efficienti di MediaQuery.of(context) perché il widget viene ricostruito solo quando cambia la specifica proprietà osservata, non l'intero MediaQueryData.

LayoutBuilder: adattarsi allo spazio del genitore

Mentre MediaQuery guarda l'intero schermo, LayoutBuilder fornisce i vincoli imposti dal widget genitore. È lo strumento ideale per componenti riutilizzabili che devono adattarsi al contenitore in cui vengono inseriti.

LayoutBuilder(
  builder: (context, constraints) {
    if (constraints.maxWidth < 600) {
      return const _LayoutMobile();
    } else if (constraints.maxWidth < 1200) {
      return const _LayoutTablet();
    } else {
      return const _LayoutDesktop();
    }
  },
)

Definire i breakpoint

Un approccio ordinato consiste nel centralizzare i breakpoint in una classe dedicata, così da riutilizzarli in tutta l'app.

class Breakpoints {
  static const double mobile = 600;
  static const double tablet = 1200;
}

enum DeviceType { mobile, tablet, desktop }

DeviceType getDeviceType(double width) {
  if (width < Breakpoints.mobile) return DeviceType.mobile;
  if (width < Breakpoints.tablet) return DeviceType.tablet;
  return DeviceType.desktop;
}

Widget flessibili: Flexible ed Expanded

All'interno di Row e Column, Expanded e Flexible distribuiscono lo spazio in proporzione, evitando valori di larghezza fissi.

Row(
  children: [
    Expanded(flex: 2, child: SidebarWidget()),
    Expanded(flex: 5, child: ContentWidget()),
  ],
)

Usa FractionallySizedBox quando vuoi che un figlio occupi una frazione dello spazio disponibile:

FractionallySizedBox(
  widthFactor: 0.8,
  child: Card(child: Text('Occupo l\'80% della larghezza')),
)

Un esempio pratico: lista che diventa griglia

Un pattern comune è mostrare una lista su mobile e una griglia su schermi più larghi.

LayoutBuilder(
  builder: (context, constraints) {
    final crossAxisCount = (constraints.maxWidth / 300).floor().clamp(1, 4);
    return GridView.builder(
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: crossAxisCount,
        mainAxisSpacing: 12,
        crossAxisSpacing: 12,
      ),
      itemCount: prodotti.length,
      itemBuilder: (context, index) => ProductCard(prodotti[index]),
    );
  },
)

In alternativa, SliverGridDelegateWithMaxCrossAxisExtent calcola automaticamente il numero di colonne in base a una larghezza massima desiderata per cella.

Layout adattivi con i widget di Material

Flutter offre widget pensati per grandi schermi. NavigationRail sostituisce la BottomNavigationBar su tablet e desktop.

Scaffold(
  body: Row(
    children: [
      if (isWideScreen)
        NavigationRail(
          selectedIndex: _index,
          onDestinationSelected: (i) => setState(() => _index = i),
          destinations: const [
            NavigationRailDestination(icon: Icon(Icons.home), label: Text('Home')),
            NavigationRailDestination(icon: Icon(Icons.settings), label: Text('Impostazioni')),
          ],
        ),
      Expanded(child: _pagine[_index]),
    ],
  ),
  bottomNavigationBar: isWideScreen ? null : BottomNavigationBar(/* ... */),
)

Best practice

  • Evita dimensioni fisse in pixel: preferisci Expanded, Flexible e percentuali.
  • Usa LayoutBuilder per i componenti, MediaQuery per decisioni a livello di schermata.
  • Gestisci il testo con FittedBox o AutoSizeText per evitare overflow.
  • Testa su più dimensioni con Device Preview o semplicemente ridimensionando la finestra su desktop e web.
  • Rispetta i SafeArea avvolgendo i contenuti in SafeArea per evitare notch e barre di sistema.

Conclusione

Costruire UI responsive in Flutter non richiede librerie esterne: MediaQuery, LayoutBuilder e i widget flessibili coprono la maggior parte dei casi d'uso. Centralizzando i breakpoint e ragionando in termini di spazio disponibile anziché di pixel fissi, otterrai app che appaiono curate su qualsiasi dispositivo, dallo smartphone al desktop.