Effettuare chiamate HTTP REST in Flutter con il pacchetto http
GuideIntermedio35 min Flutter 3.x

Effettuare chiamate HTTP REST in Flutter con il pacchetto http

Quasi tutte le app reali comunicano con un server remoto per scaricare o inviare dati. In questo tutorial vedremo come consumare una API REST in Flutter usando il pacchetto ufficiale http.

Useremo la API pubblica JSONPlaceholder per scaricare una lista di post, trasformeremo la risposta JSON in oggetti Dart tipizzati e li mostreremo in una ListView gestendo correttamente gli stati di caricamento ed errore.

Al termine avrai una base solida e riutilizzabile per integrare qualsiasi servizio web nelle tue app.

  1. 1

    Aggiungere la dipendenza http

    Per prima cosa aggiungiamo il pacchetto http al progetto. Puoi farlo da terminale nella cartella del progetto:

    flutter pub add http
    

    Questo comando inserisce automaticamente la dipendenza nel file pubspec.yaml. Verifica che sia presente sotto la sezione dependencies.

    Se sviluppi per Android, ricorda che l'app ha bisogno del permesso di accesso a Internet: di solito è già incluso nel AndroidManifest.xml di debug, ma per la build di release aggiungi <uses-permission android:name="android.permission.INTERNET"/>.

    dependencies:
      flutter:
        sdk: flutter
      http: ^1.2.0

    Risultato atteso

    Il pacchetto http compare nel pubspec.yaml e `flutter pub get` termina senza errori.

  2. 2

    Creare il modello dati Post

    Le API REST restituiscono JSON: è buona pratica convertirlo in una classe Dart tipizzata invece di lavorare con Map grezze.

    Creiamo il file lib/models/post.dart con un costruttore factory fromJson che mappa i campi del JSON sulle proprietà della classe.

    class Post {
      final int id;
      final int userId;
      final String title;
      final String body;
    
      Post({
        required this.id,
        required this.userId,
        required this.title,
        required this.body,
      });
    
      factory Post.fromJson(Map<String, dynamic> json) {
        return Post(
          id: json['id'] as int,
          userId: json['userId'] as int,
          title: json['title'] as String,
          body: json['body'] as String,
        );
      }
    }

    Risultato atteso

    Una classe Post riutilizzabile in grado di costruirsi a partire da una mappa JSON.

  3. 3

    Scrivere il servizio API

    Separiamo la logica di rete dall'interfaccia creando una classe di servizio. In lib/services/post_service.dart eseguiamo una richiesta GET all'endpoint dei post.

    Controlliamo lo statusCode: se è 200 decodifichiamo il body con jsonDecode e mappiamo ogni elemento della lista in un Post. In caso contrario lanciamo un'eccezione gestibile a monte.

    import 'dart:convert';
    import 'package:http/http.dart' as http;
    import '../models/post.dart';
    
    class PostService {
      static const _baseUrl = 'https://jsonplaceholder.typicode.com';
    
      Future<List<Post>> fetchPosts() async {
        final url = Uri.parse('$_baseUrl/posts');
        final response = await http.get(url);
    
        if (response.statusCode == 200) {
          final List<dynamic> data = jsonDecode(response.body);
          return data.map((json) => Post.fromJson(json)).toList();
        } else {
          throw Exception('Errore nel caricamento dei post: ${response.statusCode}');
        }
      }
    }

    Risultato atteso

    Il metodo fetchPosts() restituisce un Future<List<Post>> con i dati provenienti dal server.

  4. 4

    Mostrare i dati con FutureBuilder

    Usiamo FutureBuilder per collegare il Future del servizio all'interfaccia. Questo widget gestisce automaticamente i tre stati: in attesa, errore e dati pronti.

    Importante: chiama fetchPosts() una sola volta salvando il Future in una variabile dello State (in initState), altrimenti la richiesta verrebbe ripetuta ad ogni rebuild.

    import 'package:flutter/material.dart';
    import 'models/post.dart';
    import 'services/post_service.dart';
    
    class PostListPage extends StatefulWidget {
      const PostListPage({super.key});
    
      @override
      State<PostListPage> createState() => _PostListPageState();
    }
    
    class _PostListPageState extends State<PostListPage> {
      late Future<List<Post>> _postsFuture;
      final _service = PostService();
    
      @override
      void initState() {
        super.initState();
        _postsFuture = _service.fetchPosts();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: const Text('Post dal server')),
          body: FutureBuilder<List<Post>>(
            future: _postsFuture,
            builder: (context, snapshot) {
              if (snapshot.connectionState == ConnectionState.waiting) {
                return const Center(child: CircularProgressIndicator());
              }
              if (snapshot.hasError) {
                return Center(child: Text('Errore: ${snapshot.error}'));
              }
              final posts = snapshot.data ?? [];
              return ListView.builder(
                itemCount: posts.length,
                itemBuilder: (context, index) {
                  final post = posts[index];
                  return ListTile(
                    leading: CircleAvatar(child: Text('${post.id}')),
                    title: Text(post.title),
                    subtitle: Text(
                      post.body,
                      maxLines: 2,
                      overflow: TextOverflow.ellipsis,
                    ),
                  );
                },
              );
            },
          ),
        );
      }
    }

    Risultato atteso

    All'avvio compare uno spinner, poi la lista dei post scaricati dal server.

  5. 5

    Aggiungere il pull-to-refresh

    Miglioriamo l'esperienza utente consentendo di ricaricare i dati trascinando verso il basso. Avvolgiamo la ListView in un RefreshIndicator e, nel callback onRefresh, riassegniamo il Future dentro un setState.

    Nota: per far comparire l'indicatore di refresh la lista deve essere sempre scrollabile, quindi usiamo physics: AlwaysScrollableScrollPhysics().

    Future<void> _refresh() async {
      setState(() {
        _postsFuture = _service.fetchPosts();
      });
      await _postsFuture;
    }
    
    // All'interno del builder, dopo aver ottenuto posts:
    return RefreshIndicator(
      onRefresh: _refresh,
      child: ListView.builder(
        physics: const AlwaysScrollableScrollPhysics(),
        itemCount: posts.length,
        itemBuilder: (context, index) {
          final post = posts[index];
          return ListTile(
            leading: CircleAvatar(child: Text('${post.id}')),
            title: Text(post.title),
            subtitle: Text(
              post.body,
              maxLines: 2,
              overflow: TextOverflow.ellipsis,
            ),
          );
        },
      ),
    );

    Risultato atteso

    Trascinando la lista verso il basso i dati vengono ricaricati dal server.

  6. 6

    Gestire timeout ed errori di rete

    In produzione la rete può essere lenta o assente. Aggiungiamo un timeout alla richiesta e gestiamo le eccezioni di connessione con messaggi chiari per l'utente.

    Il metodo .timeout() lancia una TimeoutException se il server non risponde in tempo, mentre SocketException indica l'assenza di connessione.

    import 'dart:async';
    import 'dart:io';
    import 'dart:convert';
    import 'package:http/http.dart' as http;
    import '../models/post.dart';
    
    class PostService {
      static const _baseUrl = 'https://jsonplaceholder.typicode.com';
    
      Future<List<Post>> fetchPosts() async {
        final url = Uri.parse('$_baseUrl/posts');
        try {
          final response = await http
              .get(url)
              .timeout(const Duration(seconds: 10));
    
          if (response.statusCode == 200) {
            final List<dynamic> data = jsonDecode(response.body);
            return data.map((json) => Post.fromJson(json)).toList();
          } else {
            throw Exception('Risposta non valida: ${response.statusCode}');
          }
        } on SocketException {
          throw Exception('Nessuna connessione a Internet');
        } on TimeoutException {
          throw Exception('Il server non risponde, riprova più tardi');
        }
      }
    }

    Risultato atteso

    L'app mostra messaggi comprensibili in caso di assenza di rete o server lento, senza crashare.

CondividiXLinkedInFacebookWhatsApp

Commenti (0)

Ancora nessun commento. Inizia tu!