Flutter 网络请求完全指南

引言

网络请求是移动应用开发的核心。本文将深入探讨 Flutter 网络请求的各种方式和最佳实践。

基础概念回顾

HTTP 方法

  • GET: 获取资源
  • POST: 创建资源
  • PUT: 更新资源
  • DELETE: 删除资源
  • PATCH: 部分更新

状态码

  • 2xx: 成功
  • 3xx: 重定向
  • 4xx: 客户端错误
  • 5xx: 服务器错误

常用库

  • http: Dart 官方库
  • dio: 强大的 HTTP 客户端
  • retrofit: 类型安全的 HTTP 客户端

高级技巧一:使用 Dio

基础配置

import 'package:dio/dio.dart';

final dio = Dio(BaseOptions(
  baseUrl: 'https://api.example.com',
  connectTimeout: const Duration(seconds: 5),
  receiveTimeout: const Duration(seconds: 5),
));

GET 请求

Future<User> getUser(String id) async {
  try {
    final response = await dio.get('/users/$id');
    return User.fromJson(response.data);
  } catch (e) {
    throw Exception('Failed to fetch user');
  }
}

POST 请求

Future<User> createUser(User user) async {
  try {
    final response = await dio.post(
      '/users',
      data: user.toJson(),
    );
    return User.fromJson(response.data);
  } catch (e) {
    throw Exception('Failed to create user');
  }
}

拦截器

dio.interceptors.add(InterceptorsWrapper(
  onRequest: (options, handler) {
    options.headers['Authorization'] = 'Bearer token';
    return handler.next(options);
  },
  onResponse: (response, handler) {
    return handler.next(response);
  },
  onError: (DioException e, handler) {
    print('Error: ${e.message}');
    return handler.next(e);
  },
));

高级技巧二:使用 Retrofit

定义接口

part 'api_service.g.dart';

@RestApi(baseUrl: 'https://api.example.com')
abstract class ApiService {
  factory ApiService(Dio dio, {String baseUrl}) = _ApiService;

  @GET('/users')
  Future<List<User>> getUsers();

  @GET('/users/{id}')
  Future<User> getUser(@Path('id') String id);

  @POST('/users')
  Future<User> createUser(@Body() User user);

  @PUT('/users/{id}')
  Future<User> updateUser(
    @Path('id') String id,
    @Body() User user,
  );

  @DELETE('/users/{id}')
  Future<void> deleteUser(@Path('id') String id);
}

生成代码

flutter pub run build_runner build

使用服务

final dio = Dio();
final apiService = ApiService(dio);

final users = await apiService.getUsers();
final user = await apiService.getUser('1');

高级技巧三:错误处理

自定义异常

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

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

  @override
  String toString() => 'ApiException: $statusCode - $message';
}

统一错误处理

Future<T> handleApiCall<T>(Future<T> Function() call) async {
  try {
    return await call();
  } on DioException catch (e) {
    if (e.response != null) {
      throw ApiException(
        statusCode: e.response!.statusCode,
        message: e.response!.data['message'] ?? 'Unknown error',
      );
    } else {
      throw ApiException(
        message: e.message ?? 'Network error',
      );
    }
  } catch (e) {
    throw ApiException(message: e.toString());
  }
}

// 使用
final user = await handleApiCall(() => apiService.getUser('1'));

高级技巧四:请求取消

CancelToken cancelToken = CancelToken();

dio.get(
  '/users',
  cancelToken: cancelToken,
);

// 取消请求
cancelToken.cancel('Request cancelled');

实战案例:完整 API 服务

class ApiService {
  final Dio _dio;

  ApiService() : _dio = Dio(BaseOptions(
    baseUrl: 'https://api.example.com',
    headers: {'Accept': 'application/json'},
  )) {
    _setupInterceptors();
  }

  void _setupInterceptors() {
    _dio.interceptors.add(InterceptorsWrapper(
      onRequest: (options, handler) {
        final token = _getToken();
        if (token != null) {
          options.headers['Authorization'] = 'Bearer $token';
        }
        handler.next(options);
      },
      onResponse: (response, handler) {
        handler.next(response);
      },
      onError: (e, handler) {
        if (e.response?.statusCode == 401) {
          _handleUnauthorized();
        }
        handler.next(e);
      },
    ));
  }

  Future<User> getUser(String id) async {
    final response = await _dio.get('/users/$id');
    return User.fromJson(response.data);
  }

  Future<List<User>> getUsers({int page = 1, int limit = 10}) async {
    final response = await _dio.get(
      '/users',
      queryParameters: {'page': page, 'limit': limit},
    );
    return (response.data as List).map((e) => User.fromJson(e)).toList();
  }

  Future<User> createUser(User user) async {
    final response = await _dio.post('/users', data: user.toJson());
    return User.fromJson(response.data);
  }

  Future<void> deleteUser(String id) async {
    await _dio.delete('/users/$id');
  }

  String? _getToken() {
    // 从存储获取 token
    return 'user_token';
  }

  void _handleUnauthorized() {
    // 处理未授权
  }
}

实战案例:网络状态管理

enum NetworkStatus { loading, success, error, initial }

class NetworkState<T> {
  final NetworkStatus status;
  final T? data;
  final String? errorMessage;

  const NetworkState._({
    required this.status,
    this.data,
    this.errorMessage,
  });

  const NetworkState.loading() : this._(status: NetworkStatus.loading);
  const NetworkState.success(T data) : this._(status: NetworkStatus.success, data: data);
  const NetworkState.error(String message) : this._(status: NetworkStatus.error, errorMessage: message);
  const NetworkState.initial() : this._(status: NetworkStatus.initial);
}

实战案例:带缓存的请求

class CachedApiService {
  final ApiService _apiService;
  final Map<String, dynamic> _cache = {};
  final Duration _cacheDuration = const Duration(minutes: 5);
  final Map<String, DateTime> _cacheTimestamps = {};

  CachedApiService(this._apiService);

  Future<T> getWithCache<T>(
    String key,
    Future<T> Function() fetchFunction,
  ) async {
    if (_isCacheValid(key)) {
      return _cache[key] as T;
    }

    final result = await fetchFunction();
    _cache[key] = result;
    _cacheTimestamps[key] = DateTime.now();
    return result;
  }

  bool _isCacheValid(String key) {
    final timestamp = _cacheTimestamps[key];
    if (timestamp == null) return false;
    return DateTime.now().difference(timestamp) < _cacheDuration;
  }

  void clearCache() {
    _cache.clear();
    _cacheTimestamps.clear();
  }
}

常见问题与解决方案

Q1:请求超时?

A:设置超时时间:

Dio(BaseOptions(
  connectTimeout: const Duration(seconds: 10),
  receiveTimeout: const Duration(seconds: 10),
))

Q2:认证失败?

A:添加拦截器:

dio.interceptors.add(InterceptorsWrapper(
  onRequest: (options, handler) {
    options.headers['Authorization'] = 'Bearer token';
    handler.next(options);
  },
));

Q3:网络错误?

A:处理 DioException:

try {
  final response = await dio.get('/users');
} on DioException catch (e) {
  if (e.type == DioExceptionType.connectionError) {
    // 网络错误
  }
}

最佳实践

1. 使用单例模式

class ApiService {
  static final ApiService _instance = ApiService._internal();

  factory ApiService() => _instance;

  ApiService._internal() {
    // 初始化
  }
}

2. 分离 API 层

// 数据层
class UserRepository {
  final ApiService _apiService;

  Future<User> getUser(String id) {
    return _apiService.getUser(id);
  }
}

3. 使用模型类

class User {
  final String id;
  final String name;

  User({required this.id, required this.name});

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'],
      name: json['name'],
    );
  }

  Map<String, dynamic> toJson() {
    return {
      'id': id,
      'name': name,
    };
  }
}

总结

Flutter 网络请求是构建数据驱动应用的核心。通过本文的学习,你应该能够:

  1. 使用 Dio 进行网络请求
  2. 使用 Retrofit 实现类型安全
  3. 处理错误和异常
  4. 取消请求
  5. 实现缓存机制
  6. 管理网络状态

掌握这些技巧,能够帮助你构建更加稳定和可靠的应用。

更多推荐