Implementare il pull-to-refresh in Flutter con RefreshIndicator
GuidePrincipiante25 min Flutter 3.x

Implementare il pull-to-refresh in Flutter con RefreshIndicator

Il gesto "trascina per aggiornare" (pull-to-refresh) è un pattern ormai universale nelle app mobili: l'utente trascina verso il basso l'inizio di una lista per ricaricare i contenuti.

In questo tutorial vedremo come implementarlo in Flutter usando il widget RefreshIndicator, integrandolo con una ListView e una funzione asincrona che simula il caricamento di nuovi dati. Affronteremo anche un caso comune: cosa fare quando la lista è troppo corta per essere trascinata.

  1. 1

    Creare la struttura base dell'app

    Partiamo da un'app minimale con uno StatefulWidget che ospiterà la nostra lista. Usiamo uno StatefulWidget perché dovremo aggiornare lo stato (i dati) dopo ogni refresh.

    Definiamo una lista di elementi che faranno da contenuto iniziale.

    import 'package:flutter/material.dart';
    
    void main() => runApp(const MyApp());
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Pull to Refresh Demo',
          theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.indigo),
          home: const HomePage(),
        );
      }
    }
    
    class HomePage extends StatefulWidget {
      const HomePage({super.key});
    
      @override
      State<HomePage> createState() => _HomePageState();
    }
    
    class _HomePageState extends State<HomePage> {
      List<String> _items = List.generate(10, (i) => 'Elemento ${i + 1}');
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: const Text('Pull to Refresh')),
          body: const Placeholder(),
        );
      }
    }

    Risultato atteso

    L'app si avvia mostrando una AppBar e un Placeholder al posto del contenuto.

  2. 2

    Simulare il caricamento dei dati

    Creiamo una funzione asincrona _refreshData che simula una chiamata di rete con un ritardo. Al termine genera nuovi dati e aggiorna lo stato.

    RefreshIndicator richiede che la funzione di callback restituisca un Future: l'indicatore di caricamento rimane visibile finché il Future non si completa.

    Future<void> _refreshData() async {
      // Simula una chiamata di rete
      await Future.delayed(const Duration(seconds: 2));
    
      // Genera nuovi dati casuali
      final nuovi = List.generate(
        10,
        (i) => 'Aggiornato ${DateTime.now().second} - #${i + 1}',
      );
    
      // Aggiorna lo stato solo se il widget è ancora montato
      if (!mounted) return;
      setState(() {
        _items = nuovi;
      });
    }

    Risultato atteso

    Abbiamo una funzione pronta che, dopo 2 secondi, sostituisce gli elementi della lista.

  3. 3

    Integrare RefreshIndicator con ListView

    Ora avvolgiamo la ListView dentro un RefreshIndicator. Il parametro onRefresh riceve la nostra funzione asincrona.

    È importante che il figlio del RefreshIndicator sia un widget scorrevole (come ListView), perché il gesto di trascinamento si attiva proprio sullo scroll.

    @override
    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(title: const Text('Pull to Refresh')),
        body: RefreshIndicator(
          onRefresh: _refreshData,
          child: ListView.separated(
            itemCount: _items.length,
            separatorBuilder: (_, __) => const Divider(height: 1),
            itemBuilder: (context, index) {
              return ListTile(
                leading: const Icon(Icons.label_outline),
                title: Text(_items[index]),
              );
            },
          ),
        ),
      );
    }

    Risultato atteso

    Trascinando la lista verso il basso appare l'indicatore circolare; dopo 2 secondi i dati si aggiornano.

  4. 4

    Personalizzare l'aspetto dell'indicatore

    RefreshIndicator espone alcuni parametri per adattarlo al design della tua app: color per il colore dello spinner, backgroundColor per lo sfondo e displacement per la distanza dal bordo superiore a cui appare l'indicatore.

    RefreshIndicator(
      onRefresh: _refreshData,
      color: Colors.indigo,
      backgroundColor: Colors.indigo.shade50,
      displacement: 40,
      strokeWidth: 3,
      child: ListView.separated(
        itemCount: _items.length,
        separatorBuilder: (_, __) => const Divider(height: 1),
        itemBuilder: (context, index) => ListTile(
          leading: const Icon(Icons.label_outline),
          title: Text(_items[index]),
        ),
      ),
    )

    Risultato atteso

    L'indicatore di refresh appare con i colori personalizzati coerenti con il tema.

  5. 5

    Gestire liste corte e contenuto non scorrevole

    Un problema comune: se la lista contiene pochi elementi e non riempie lo schermo, non c'è abbastanza spazio per fare scroll e quindi il gesto di pull-to-refresh non si attiva.

    La soluzione è impostare physics: const AlwaysScrollableScrollPhysics() sulla ListView, così lo scroll (e quindi il refresh) è sempre possibile anche con poco contenuto.

    RefreshIndicator(
      onRefresh: _refreshData,
      child: ListView.separated(
        physics: const AlwaysScrollableScrollPhysics(),
        itemCount: _items.length,
        separatorBuilder: (_, __) => const Divider(height: 1),
        itemBuilder: (context, index) => ListTile(
          leading: const Icon(Icons.label_outline),
          title: Text(_items[index]),
        ),
      ),
    )

    Risultato atteso

    Il pull-to-refresh funziona anche quando la lista ha pochi elementi e non riempie lo schermo.

  6. 6

    Avviare il refresh in modo programmatico

    A volte serve attivare il refresh senza il gesto dell'utente, ad esempio appena la schermata viene aperta. Si può usare una GlobalKey<RefreshIndicatorState> e chiamare .show() nel metodo initState.

    Attenzione: il primo frame deve essere già renderizzato, quindi usiamo WidgetsBinding.instance.addPostFrameCallback.

    final _refreshKey = GlobalKey<RefreshIndicatorState>();
    
    @override
    void initState() {
      super.initState();
      WidgetsBinding.instance.addPostFrameCallback((_) {
        _refreshKey.currentState?.show();
      });
    }
    
    // Nel build, assegna la key:
    // RefreshIndicator(
    //   key: _refreshKey,
    //   onRefresh: _refreshData,
    //   child: ...
    // )

    Risultato atteso

    All'apertura della schermata l'indicatore di refresh si attiva automaticamente e carica i dati.

CondividiXLinkedInFacebookWhatsApp

Commenti (0)

Ancora nessun commento. Inizia tu!