Flutter: Rede e API REST

Última atualização: 2026-08-26

Um app sem rede é uma ilha — requisições de API são a ponte para o continente.

📋 Pré-requisitos: Você precisa dominar o seguinte primeiro

1. O Que Você Vai Aprender


2. Uma História Real de Falhas em Requisições de Rede

(1) O Problema: Erros de Rede Derrubam o App

No dia de lançamento do ShopApp, usuários com conexões instáveis obtiveram crashes de tela branca — sem try-catch, sem timeout, sem retry. Pior ainda, quando o Token expirava, todas as requisições retornavam 401 e o app não tinha mecanismo de auto-refresh, forçando os usuários a fazer login novamente. 2 mil avaliações negativas em um dia.

(2) A Solução com Interceptor do Dio

O mecanismo de interceptores do Dio pode anexar tokens uniformemente antes das requisições saírem, tratar automaticamente respostas 401 renovando tokens, e tentar novamente em timeouts de rede.

DART
import 'package:dio/dio.dart';

// ⚙️ **Install dependency**: flutter pub add dio

// Assume token and refreshToken are defined elsewhere
// String token = '...';
// Future<String> refreshToken() async { ... }

// Auth interceptor: auto-attach token + auto-refresh on 401
dio.interceptors.add(InterceptorsWrapper(
  onRequest: (options, handler) {
    options.headers['Authorization'] = 'Bearer $token';
    handler.next(options);
  },
  onError: (error, handler) async {
    if (error.response?.statusCode == 401) {
      token = await refreshToken();
      return handler.resolve(await dio.fetch(error.requestOptions));
    }
    handler.next(error);
  },
));
TEXT 📖 Somente leitura
> **Saída:** Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

(3) Benefício: Zero Crashes Relacionados à Rede

Após usar interceptores Dio, Bob obtém auto-refresh de token em 401, retry 3x em timeout, e exibição unificada de erros. A taxa de crash relacionada à rede cai de 5% para 0.01%.


3. Requisições HTTP Básicas

(1) Operações CRUD do Pacote http

Método Verbo HTTP Propósito
http.get GET Recuperar recursos
http.post POST Criar recursos
http.put PUT Atualizar recursos
http.delete DELETE Excluir recursos

▶ Exemplo

TEXT 📖 Somente leitura
> **Saída:** Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

: Requisições básicas com pacote http

DART
import 'package:http/http.dart' as http;
import 'dart:convert';

// ⚙️ **Install dependency**: flutter pub add http

// Custom class definition sources:
// - Product: see Lesson 5 json_serializable model in this lesson
// - ApiException: see Lesson 6 unified error handling in this lesson

// Assume token is defined elsewhere
// String token = '...';

class ProductApi {
  static const String baseUrl = 'https://api.shopapp.com/v1';

  Future<List<Product>> getProducts({int page = 1}) async {
    final response = await http.get(
      Uri.parse('$baseUrl/products?page=$page'),
      headers: {'Authorization': 'Bearer $token'},
    );
    if (response.statusCode == 200) {
      final data = jsonDecode(response.body);
      return (data['items'] as List).map((j) => Product.fromJson(j)).toList();
    }
    throw ApiException(response.statusCode, response.body);
  }

  Future<Product> createProduct(Product product) async {
    final response = await http.post(
      Uri.parse('$baseUrl/products'),
      headers: {'Content-Type': 'application/json', 'Authorization': 'Bearer $token'},
      body: jsonEncode(product.toJson()),
    );
    if (response.statusCode == 201) {
      return Product.fromJson(jsonDecode(response.body));
    }
    throw ApiException(response.statusCode, response.body);
  }
}
TEXT 📖 Somente leitura
> **Saída:** Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

4. Dio em Profundidade

(1) Comparação http vs Dio

Recurso http Dio
CRUD básico ✅ ✅
Interceptores ❌ ✅
CancelToken ❌ ✅
Upload FormData Manual ✅
Retry com timeout Manual ✅
Log de requisições Manual ✅ (dio_logger)
Configuração global ❌ ✅ BaseOptions

▶ Exemplo

TEXT 📖 Somente leitura
> **Saída:** Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

: Configuração básica do Dio

DART
import 'package:dio/dio.dart';

// ⚙️ **Install dependency**: flutter pub add dio

// Custom class definition sources:
// - Product: see Lesson 5 json_serializable model in this lesson
// - Order/Cart: see Lesson 14 Phase 2 practice

final dio = Dio(BaseOptions(
  baseUrl: 'https://api.shopapp.com/v1',
  connectTimeout: const Duration(seconds: 10),
  receiveTimeout: const Duration(seconds: 30),
  headers: {
    'Content-Type': 'application/json',
    'Accept': 'application/json',
  },
));

// GET request
Future<List<Product>> getProducts({int page = 1}) async {
  final response = await dio.get('/products', queryParameters: {'page': page});
  return (response.data['items'] as List).map((j) => Product.fromJson(j)).toList();
}

// POST request
Future<Order> createOrder(Cart cart) async {
  final response = await dio.post('/orders', data: cart.toJson());
  return Order.fromJson(response.data);
}
TEXT 📖 Somente leitura
> **Saída:** Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

▶ Exemplo

TEXT 📖 Somente leitura
> **Saída:** Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

: Interceptor de autenticação + Renovação de Token

DART
import 'package:dio/dio.dart';

// Custom class definition sources:
// - SecureStorage: see Lesson 13 Flutter Secure Storage
// - AuthService: see Lesson 14 Auth Notifier

class AuthInterceptor extends Interceptor {
  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    final token = SecureStorage.getToken();
    if (token != null) {
      options.headers['Authorization'] = 'Bearer $token';
    }
    handler.next(options);
  }

  @override
  void onError(DioException err, ErrorInterceptorHandler handler) async {
    if (err.response?.statusCode == 401) {
      try {
        final newToken = await _refreshToken();
        SecureStorage.saveToken(newToken);
        // Retry original request with new token
        err.requestOptions.headers['Authorization'] = 'Bearer $newToken';
        final response = await dio.fetch(err.requestOptions);
        return handler.resolve(response);
      } catch (_) {
        // Refresh failed, redirect to login
        AuthService.logout();
      }
    }
    handler.next(err);
  }

  Future<String> _refreshToken() async {
    final refreshToken = SecureStorage.getRefreshToken();
    final response = await Dio().post(
      'https://api.shopapp.com/v1/auth/refresh',
      data: {'refresh_token': refreshToken},
    );
    return response.data['access_token'];
  }
}
TEXT 📖 Somente leitura
> **Saída:** Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

▶ Exemplo

TEXT 📖 Somente leitura
> **Saída:** Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

: Cancelamento de requisição com CancelToken

DART
import 'package:dio/dio.dart';

// ⚙️ **Install dependency**: flutter pub add dio

CancelToken? _cancelToken;

void searchProducts(String query) {
  _cancelToken?.cancel('New search request');
  _cancelToken = CancelToken();
  dio.get('/products/search', queryParameters: {'q': query},
    cancelToken: _cancelToken);
}

// Cancel on dispose
@override
void dispose() {
  _cancelToken?.cancel();
  super.dispose();
}
TEXT 📖 Somente leitura
> **Saída:** Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

5. Serialização JSON

(1) Comparação de Métodos de Serialização

Método Prós Contras Caso de Uso
fromJson/toJson manual Sem dependências, controle total Repetitivo, propenso a erros Poucos modelos simples
json_serializable Segurança de tipos, auto-gerado Requer build_runner Muitos modelos
freezed Imutável + copyWith + union Mais dependências Modelos de domínio complexos

▶ Exemplo

TEXT 📖 Somente leitura
> **Saída:** Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

: Modelo com json_serializable

DART
import 'package:json_annotation/json_annotation.dart';

// ⚙️ **Install dependency**: flutter pub add json_annotation
// ⚙️ **Dev dependency**: flutter pub add --dev json_serializable build_runner

part 'product.g.dart';

@JsonSerializable()
class Product {
  final int id;
  final String name;
  @JsonKey(name: 'unit_price')
  final double price;
  @JsonKey(name: 'image_url')
  final String? imageUrl;
  final double rating;

  const Product({
    required this.id,
    required this.name,
    required this.price,
    this.imageUrl,
    this.rating = 0.0,
  });

  factory Product.fromJson(Map<String, dynamic> json) => _$ProductFromJson(json);
  Map<String, dynamic> toJson() => _$ProductToJson(this);
}

// Run: dart run build_runner build --delete-conflicting-outputs
TEXT 📖 Somente leitura
> **Saída:** Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

6. Tratamento de Erros Unificado

▶ Exemplo

TEXT 📖 Somente leitura
> **Saída:** Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

: ApiException + interceptor de erro

DART
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';

// ⚙️ **Install dependency**: flutter pub add dio

class ApiException implements Exception {
  final int? statusCode;
  final String message;
  final String? errorCode;

  ApiException(this.statusCode, this.message, {this.errorCode});

  factory ApiException.fromDioError(DioException error) {
    switch (error.type) {
      case DioExceptionType.connectionTimeout:
      case DioExceptionType.sendTimeout:
      case DioExceptionType.receiveTimeout:
        return ApiException(null, 'Connection timeout. Please try again.');
      case DioExceptionType.connectionError:
        return ApiException(null, 'No internet connection.');
      case DioExceptionType.badResponse:
        final status = error.response?.statusCode;
        final msg = error.response?.data['message'] ?? 'Server error';
        if (status == 401) return ApiException(401, 'Session expired. Please login again.');
        if (status == 403) return ApiException(403, 'Access denied.');
        if (status == 404) return ApiException(404, 'Resource not found.');
        if (status == 422) return ApiException(422, msg, errorCode: error.response?.data['code']);
        if (status != null && status >= 500) return ApiException(status, 'Server error. Please try later.');
        return ApiException(status, msg);
      default:
        return ApiException(null, 'Unexpected error.');
    }
  }
}

// Usage in UI
try {
  final products = await productRepo.getProducts();
} on ApiException catch (e) {
  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(content: Text(e.message)),
  );
}
TEXT 📖 Somente leitura
> **Saída:** Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

7. Exemplo Completo: Repository Dio do ShopApp

DART
import 'package:dio/dio.dart';

// ⚙️ **Install dependency**: flutter pub add dio

// Custom class definition sources:
// - Product: see Lesson 5 json_serializable model in this lesson
// - CartItem/Address/Order: see Lesson 14 Phase 2 practice
// - AuthInterceptor: see Lesson 4 Auth interceptor in this lesson

class ProductRepository {
  final Dio _dio;

  ProductRepository(this._dio);

  Future<List<Product>> getProducts({
    int page = 1,
    int limit = 20,
    String? category,
    String? search,
  }) async {
    final params = {'page': page, 'limit': limit};
    if (category != null) params['category'] = category;
    if (search != null) params['q'] = search;

    final response = await _dio.get('/products', queryParameters: params);
    return (response.data['items'] as List)
        .map((j) => Product.fromJson(j))
        .toList();
  }

  Future<Product> getProductById(int id) async {
    final response = await _dio.get('/products/$id');
    return Product.fromJson(response.data);
  }

  Future<Order> createOrder(List<CartItem> items, Address address) async {
    final response = await _dio.post('/orders', data: {
      'items': items.map((i) => {'product_id': i.product.id, 'quantity': i.quantity}).toList(),
      'shipping_address': address.toJson(),
    });
    return Order.fromJson(response.data);
  }

  Future<String> uploadProductImage(String filePath) async {
    final formData = FormData.fromMap({
      'image': await MultipartFile.fromFile(filePath),
    });
    final response = await _dio.post('/upload', data: formData);
    return response.data['url'];
  }
}

// Dio singleton setup
final dio = Dio(BaseOptions(
  baseUrl: 'https://api.shopapp.com/v1',
  connectTimeout: const Duration(seconds: 10),
  receiveTimeout: const Duration(seconds: 30),
))
  ..interceptors.add(AuthInterceptor())
  ..interceptors.add(LogInterceptor(requestBody: true, responseBody: true));

final productRepo = ProductRepository(dio);
TEXT 📖 Somente leitura
> **Saída:** Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.

❓ Perguntas Frequentes

P: Como escolher entre Dio e o pacote http? R: O pacote http é suficiente para projetos pequenos; escolha Dio quando precisar de interceptores, cancelamento de requisições, retry, upload FormData, etc.

P: build_runner é lento toda vez? R: Durante o desenvolvimento, use dart run build_runner watch --delete-conflicting-outputs para monitorar continuamente as mudanças de arquivo e gerar automaticamente.

P: Múltiplas requisições recebem 401 simultaneamente durante a renovação do token? R: Adicione um mecanismo de bloqueio: o primeiro 401 aciona a renovação, e as outras requisições 401 ficam na fila até a renovação ser concluída. Use um Completer para implementar isso.

P: Upload FormData de arquivos grandes causará OOM? R: MultipartFile.fromFile é carregado preguiçosamente e não lê o arquivo inteiro na memória de uma vez. Para arquivos grandes, considere upload em partes.

P: Como depurar requisições de rede no desenvolvimento? R: Adicione LogInterceptor para imprimir requisições/respostas, ou use Charles/Proxyman para captura de pacotes. Dio suporta configuração de proxy.

P: Verificação de certificado SSL falha? R: No desenvolvimento, use HttpClientAdapter para pular a verificação; em produção, os certificados devem ser verificados.


📖 Resumo


📝 Exercícios

  1. Básico (⭐): Use o pacote http para fazer uma requisição GET, buscar uma lista de produtos, e imprimir o JSON.
  2. Intermediário (⭐⭐): Envolva um Repository com Dio, adicione um interceptor de autenticação para auto-anexar Bearer Token, e auto-renovar em 401.
  3. Desafio (⭐⭐⭐): Implemente uma camada de rede completa: Dio + interceptor Auth + interceptor Log + CancelToken debounce na busca + ApiException tratamento unificado de erros + modelos json_serializable.

← Aula Anterior | Próxima Aula →

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%