Skip to content

Flutter 网络请求

网络请求概述

在 Flutter 中,网络请求是与后端服务交互的重要方式。Flutter 提供了多种网络请求方案,从基础的 HttpClient 到第三方库如 httpdio 等。

基础网络请求

1. 使用 HttpClient

HttpClient 是 Dart 内置的网络请求类,不需要额外依赖。

示例

dart
import 'dart:io';
import 'dart:convert';

Future<void> fetchData() async {
  try {
    // 创建 HttpClient
    final httpClient = HttpClient();
    
    // 创建请求
    final request = await httpClient.getUrl(Uri.parse('https://api.example.com/data'));
    
    // 发送请求并获取响应
    final response = await request.close();
    
    // 读取响应内容
    final responseBody = await response.transform(utf8.decoder).join();
    
    // 解析 JSON
    final data = jsonDecode(responseBody);
    print(data);
    
    // 关闭 HttpClient
    httpClient.close();
  } catch (e) {
    print('Error: $e');
  }
}

2. 使用 http 包

http 是 Flutter 官方推荐的网络请求包,提供了更简洁的 API。

安装

bash
flutter pub add http

示例

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

Future<void> fetchData() async {
  try {
    // 发送 GET 请求
    final response = await http.get(Uri.parse('https://api.example.com/data'));
    
    // 检查响应状态
    if (response.statusCode == 200) {
      // 解析 JSON
      final data = jsonDecode(response.body);
      print(data);
    } else {
      print('Request failed with status: ${response.statusCode}');
    }
  } catch (e) {
    print('Error: $e');
  }
}

// 发送 POST 请求
Future<void> postData() async {
  try {
    final response = await http.post(
      Uri.parse('https://api.example.com/data'),
      headers: <String, String>{
        'Content-Type': 'application/json; charset=UTF-8',
      },
      body: jsonEncode(<String, String>{
        'title': 'Flutter',
        'body': 'Network request example',
      }),
    );
    
    if (response.statusCode == 201) {
      final data = jsonDecode(response.body);
      print(data);
    } else {
      print('Request failed with status: ${response.statusCode}');
    }
  } catch (e) {
    print('Error: $e');
  }
}

高级网络请求

1. 使用 dio 包

dio 是一个功能强大的 Dart HTTP 客户端,支持拦截器、全局配置、FormData、文件上传/下载等。

安装

bash
flutter pub add dio

示例

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

// 创建 dio 实例
final dio = Dio();

Future<void> fetchData() async {
  try {
    // 发送 GET 请求
    final response = await dio.get('https://api.example.com/data');
    print(response.data);
  } catch (e) {
    print('Error: $e');
  }
}

// 发送 POST 请求
Future<void> postData() async {
  try {
    final response = await dio.post('https://api.example.com/data', data: {
      'title': 'Flutter',
      'body': 'Dio example',
    });
    print(response.data);
  } catch (e) {
    print('Error: $e');
  }
}

// 配置 dio
void configureDio() {
  // 设置基础 URL
  dio.options.baseUrl = 'https://api.example.com';
  // 设置超时时间
  dio.options.connectTimeout = Duration(seconds: 5);
  dio.options.receiveTimeout = Duration(seconds: 3);
  // 设置请求头
  dio.options.headers['Content-Type'] = 'application/json';
}

2. 拦截器

dio 的拦截器可以用于添加认证令牌、处理错误等。

dart
// 添加请求拦截器
dio.interceptors.add(InterceptorsWrapper(
  onRequest: (options, handler) {
    // 在发送请求前添加认证令牌
    options.headers['Authorization'] = 'Bearer token';
    return handler.next(options);
  },
  onResponse: (response, handler) {
    // 处理响应
    return handler.next(response);
  },
  onError: (DioError e, handler) {
    // 处理错误
    if (e.response?.statusCode == 401) {
      // 处理未授权错误
      print('Unauthorized');
    }
    return handler.next(e);
  },
));

网络请求封装

为了代码复用和便于维护,通常会封装网络请求。

1. 创建 API 服务类

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

class ApiService {
  late Dio _dio;

  ApiService() {
    _dio = Dio();
    _configureDio();
  }

  void _configureDio() {
    _dio.options.baseUrl = 'https://api.example.com';
    _dio.options.connectTimeout = Duration(seconds: 5);
    _dio.options.receiveTimeout = Duration(seconds: 3);
    _dio.options.headers['Content-Type'] = 'application/json';

    // 添加拦截器
    _dio.interceptors.add(InterceptorsWrapper(
      onRequest: (options, handler) {
        // 添加认证令牌
        final token = getToken();
        if (token != null) {
          options.headers['Authorization'] = 'Bearer $token';
        }
        return handler.next(options);
      },
      onError: (DioError e, handler) {
        // 统一错误处理
        print('API Error: ${e.message}');
        return handler.next(e);
      },
    ));
  }

  Future<dynamic> get(String path, {Map<String, dynamic>? queryParameters}) async {
    try {
      final response = await _dio.get(path, queryParameters: queryParameters);
      return response.data;
    } catch (e) {
      throw Exception('Failed to get data: $e');
    }
  }

  Future<dynamic> post(String path, {dynamic data}) async {
    try {
      final response = await _dio.post(path, data: data);
      return response.data;
    } catch (e) {
      throw Exception('Failed to post data: $e');
    }
  }

  Future<dynamic> put(String path, {dynamic data}) async {
    try {
      final response = await _dio.put(path, data: data);
      return response.data;
    } catch (e) {
      throw Exception('Failed to put data: $e');
    }
  }

  Future<dynamic> delete(String path) async {
    try {
      final response = await _dio.delete(path);
      return response.data;
    } catch (e) {
      throw Exception('Failed to delete data: $e');
    }
  }

  String? getToken() {
    // 从存储中获取令牌
    return 'your-token-here';
  }
}

// 使用
try {
  final apiService = ApiService();
  final data = await apiService.get('/users');
  print(data);
} catch (e) {
  print('Error: $e');
}

2. 响应模型

使用 Dart 的 json_serializable 包来自动生成 JSON 序列化代码。

安装

bash
flutter pub add json_annotation
flutter pub add --dev build_runner json_serializable

示例

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

part 'user.g.dart';

@JsonSerializable()
class User {
  final int id;
  final String name;
  final String email;

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

  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
  Map<String, dynamic> toJson() => _$UserToJson(this);
}

// 生成代码
// 运行命令: flutter pub run build_runner build

// 使用
final user = User.fromJson(jsonData);
print(user.name);

网络请求状态管理

1. 使用 FutureBuilder

dart
class DataPage extends StatelessWidget {
  final ApiService apiService = ApiService();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Data')),
      body: FutureBuilder(
        future: apiService.get('/users'),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return Center(child: CircularProgressIndicator());
          } else if (snapshot.hasError) {
            return Center(child: Text('Error: ${snapshot.error}'));
          } else if (snapshot.hasData) {
            final users = snapshot.data as List;
            return ListView.builder(
              itemCount: users.length,
              itemBuilder: (context, index) {
                final user = User.fromJson(users[index]);
                return ListTile(
                  title: Text(user.name),
                  subtitle: Text(user.email),
                );
              },
            );
          } else {
            return Center(child: Text('No data'));
          }
        },
      ),
    );
  }
}

2. 使用 StreamBuilder

dart
class DataPage extends StatefulWidget {
  @override
  _DataPageState createState() => _DataPageState();
}

class _DataPageState extends State<DataPage> {
  final ApiService apiService = ApiService();
  late Stream<List<User>> _usersStream;

  @override
  void initState() {
    super.initState();
    _usersStream = _fetchUsers();
  }

  Stream<List<User>> _fetchUsers() async* {
    try {
      final data = await apiService.get('/users');
      final users = (data as List).map((item) => User.fromJson(item)).toList();
      yield users;
    } catch (e) {
      throw Exception('Failed to fetch users');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Data')),
      body: StreamBuilder(
        stream: _usersStream,
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return Center(child: CircularProgressIndicator());
          } else if (snapshot.hasError) {
            return Center(child: Text('Error: ${snapshot.error}'));
          } else if (snapshot.hasData) {
            final users = snapshot.data!;
            return ListView.builder(
              itemCount: users.length,
              itemBuilder: (context, index) {
                final user = users[index];
                return ListTile(
                  title: Text(user.name),
                  subtitle: Text(user.email),
                );
              },
            );
          } else {
            return Center(child: Text('No data'));
          }
        },
      ),
    );
  }
}

网络请求最佳实践

  1. 使用 HTTPS:确保所有网络请求使用 HTTPS 协议
  2. 添加超时设置:避免请求无限期等待
  3. 处理错误:合理处理网络错误和服务器错误
  4. 使用拦截器:统一处理认证、日志等
  5. 缓存策略:合理使用缓存减少网络请求
  6. 请求取消:在页面销毁时取消未完成的请求
  7. 压缩数据:使用 gzip 等压缩方式减少数据传输量
  8. 批量请求:合并多个请求减少网络开销
  9. 使用 WebSocket:对于实时数据使用 WebSocket
  10. 监控网络状态:根据网络状态调整请求策略

总结

Flutter 提供了多种网络请求方案,从基础的 HttpClient 到功能强大的第三方库 dio。选择合适的方案并进行合理的封装,可以使网络请求代码更加简洁、可维护。同时,结合状态管理和错误处理,可以提供更好的用户体验。

基于 VitePress 的本地知识库