Nello sviluppo Flutter una necessità frequente è quella di implementare un Widget che si aggiorna in modo dinamico in base al risultato di una funzione.

Per implementare un Widget che si aggiorna in base al risultato di una funzione asincrona, ovvero una funzione che ritorna un Future, esistono due modalità principali: utilizzare uno StatelessWidget con un FutureBuilder o utilizzare uno StatefulWidget.

Nel caso dello StatelessWidget, il metodo build del Widget utilizza un FutureBuilder per gestire l’aggiornamento in base al risultato del Future. In questo modo, non c’è bisogno di preoccuparsi di mantenere lo stato del widget poiché il FutureBuilder si occupa della gestione dello stato al posto nostro.

Di seguito un esempio di StatelessWidget che utilizza un FutureBuilder:

 
import 'package:flutter/material.dart';
 
class FutureDataWidget extends StatelessWidget {
  final Future<String> future;
 
  const FutureDataWidget({Key? key, required this.future}) : super(key: key);
 
  @override
  Widget build(BuildContext context) {
    return FutureBuilder<String>(
      future: future,
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          return Text(snapshot.data!);
        } else if (snapshot.hasError) {
          return Text("Errore: ${snapshot.error}");
        }
        return const CircularProgressIndicator();
      },
    );
  }
}

Se vogliamo usare uno StatefulWidget, invece, utilizzeremo il metodo setState per gestire l’aggiornamento del widget in base al risultato del Future.

 
class FutureDataStatefulWidget extends StatefulWidget {
  final Future<String> future;
 
  const FutureDataStatefulWidget({Key? key, required this.future}) : super(key: key);
 
  @override
  FutureDataStatefulWidgetState createState() => FutureDataStatefulWidgetState();
}
 
class FutureDataStatefulWidgetState extends State<FutureDataStatefulWidget> {
  String? data;
  bool isLoading = true;
  String? error;
 
  @override
  void initState() {
    super.initState();
    _fetchData();
  }
 
  _fetchData() async {
    setState(() {
      isLoading = true;
    });
    try {
      data = await widget.future;
    } catch (e) {
      error = e.toString();
    } finally {
      setState(() {
        isLoading = false;
      });
    }
  }
 
  @override
  Widget build(BuildContext context) {
    if (isLoading) {
      return const CircularProgressIndicator();
    }
    if (error != null) {
      return Text("Errore: $error");
    }
    return Text(data!);
  }
}
 

La cosa curiosa è che FutureBuilder al suo interno è implementato come uno StatefulWidget! Pertanto FutureBuilder semplifica il lavoro perché ci astrae dal dover gestire lo stato; per lo stesso motivo, in termini di performance, non c’è una grande differenza tra le due opzioni.

Tuttavia è importante conoscere l’esistenza di entrambi i metodi per poter scegliere la soluzione più adatta al nostro caso specifico.