Skip to content

第9章:Flutter 网络请求

9.1 网络请求的概念

什么是网络请求?

在 Flutter 中,网络请求是指应用与后端服务器之间的通信过程,用于获取数据、提交数据或执行其他网络操作。

网络请求的作用

  • 获取远程数据(如用户信息、商品列表、新闻资讯)
  • 提交数据(如用户注册、登录、表单提交)
  • 上传/下载文件
  • 与后端 API 交互

常见的网络请求方法

  • GET:获取数据,参数附加在 URL 中
  • POST:提交数据,参数放在请求体中
  • PUT:更新数据
  • DELETE:删除数据
  • PATCH:部分更新数据

9.2 常用网络请求插件

Dio 插件

Dio 是 Flutter 中最常用的网络请求插件,功能强大,支持多种请求方式、拦截器、请求取消等特性。

安装

yaml
dependencies:
  dio: ^5.4.3+1

基本配置

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

// 创建 Dio 实例
final dio = Dio(
  BaseOptions(
    baseUrl: 'https://api.example.com', // 基础 URL
    connectTimeout: Duration(seconds: 10), // 连接超时
    receiveTimeout: Duration(seconds: 10), // 接收超时
    headers: {
      'Content-Type': 'application/json',
    },
  ),
);

其他网络请求插件

  • http:Flutter 官方推荐的基础网络请求库
  • retrofit:基于 Dio 的 RESTful API 客户端生成器
  • chopper:另一个 RESTful API 客户端

9.3 GET 请求

基本用法

dart
Future<void> fetchData() async {
  try {
    // 发送 GET 请求
    final response = await dio.get('/users', queryParameters: {
      'page': 1,
      'limit': 10,
    });
    
    // 处理响应数据
    print('Response: ${response.data}');
  } catch (e) {
    // 处理错误
    print('Error: $e');
  }
}

带参数的 GET 请求

dart
// 方式 1:使用 queryParameters
final response = await dio.get('/users', queryParameters: {
  'id': 123,
  'name': 'John',
});

// 方式 2:直接拼接 URL
final response = await dio.get('/users?id=123&name=John');

响应数据解析

dart
// 解析 JSON 数据
final data = response.data;
final users = data['users'] as List;
for (var user in users) {
  print('User: ${user['name']}');
}

9.4 POST 请求

基本用法

dart
Future<void> submitData() async {
  try {
    // 发送 POST 请求
    final response = await dio.post('/users', data: {
      'name': 'John Doe',
      'email': 'john@example.com',
      'password': '123456',
    });
    
    // 处理响应数据
    print('Response: ${response.data}');
  } catch (e) {
    // 处理错误
    print('Error: $e');
  }
}

不同格式的请求体

JSON 格式(默认)

dart
final response = await dio.post('/users', data: {
  'name': 'John Doe',
  'email': 'john@example.com',
});

Form-Data 格式

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

final formData = FormData.fromMap({
  'name': 'John Doe',
  'email': 'john@example.com',
  'avatar': await MultipartFile.fromFile('./avatar.png', filename: 'avatar.png'),
});

final response = await dio.post('/users', data: formData);

文本格式

dart
final response = await dio.post('/users', 
  data: 'name=John Doe&email=john@example.com',
  options: Options(
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
    },
  ),
);

9.5 响应数据解析

手动解析 JSON

dart
// 定义用户模型
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) {
    return User(
      id: json['id'],
      name: json['name'],
      email: json['email'],
    );
  }
}

// 解析响应数据
final response = await dio.get('/users/1');
final user = User.fromJson(response.data);
print('User: ${user.name}, ${user.email}');

使用 json_serializable 自动生成代码

安装依赖

yaml
dependencies:
  json_annotation: ^4.8.1

dev_dependencies:
  build_runner: ^2.4.6
  json_serializable: ^6.7.1

定义模型类

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);
}

生成代码

bash
flutter pub run build_runner build

使用生成的代码

dart
final response = await dio.get('/users/1');
final user = User.fromJson(response.data);
print('User: ${user.name}, ${user.email}');

解析复杂数据结构

dart
@JsonSerializable()
class UserList {
  final int total;
  final List<User> users;

  UserList({required this.total, required this.users});

  factory UserList.fromJson(Map<String, dynamic> json) => _$UserListFromJson(json);
  Map<String, dynamic> toJson() => _$UserListToJson(this);
}

// 解析列表数据
final response = await dio.get('/users');
final userList = UserList.fromJson(response.data);
print('Total users: ${userList.total}');
for (var user in userList.users) {
  print('User: ${user.name}');
}

9.6 网络请求异常处理

使用 try-catch 捕获异常

dart
Future<void> fetchData() async {
  try {
    final response = await dio.get('/users');
    print('Success: ${response.data}');
  } catch (e) {
    if (e is DioException) {
      // 处理 Dio 异常
      switch (e.type) {
        case DioExceptionType.connectionTimeout:
          print('Connection timeout');
          break;
        case DioExceptionType.sendTimeout:
          print('Send timeout');
          break;
        case DioExceptionType.receiveTimeout:
          print('Receive timeout');
          break;
        case DioExceptionType.badResponse:
          print('Bad response: ${e.response?.statusCode}');
          break;
        case DioExceptionType.cancel:
          print('Request cancelled');
          break;
        default:
          print('Unknown error: $e');
      }
    } else {
      // 处理其他异常
      print('Error: $e');
    }
  }
}

使用拦截器处理异常

dart
// 添加拦截器
dio.interceptors.add(
  InterceptorsWrapper(
    onRequest: (options, handler) {
      // 请求前处理
      print('Request: ${options.uri}');
      return handler.next(options);
    },
    onResponse: (response, handler) {
      // 响应后处理
      print('Response: ${response.statusCode}');
      return handler.next(response);
    },
    onError: (error, handler) {
      // 错误处理
      print('Error: ${error.message}');
      return handler.next(error);
    },
  ),
);

统一错误处理

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

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

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

Future<T> apiRequest<T>(Future<T> Function() request) async {
  try {
    return await request();
  } catch (e) {
    if (e is DioException) {
      throw ApiException(
        e.message ?? 'Network error',
        statusCode: e.response?.statusCode,
      );
    }
    throw ApiException('Unknown error: $e');
  }
}

// 使用
Future<void> fetchData() async {
  try {
    final response = await apiRequest(() => dio.get('/users'));
    print('Success: ${response.data}');
  } catch (e) {
    if (e is ApiException) {
      print('API Error: ${e.message}');
    } else {
      print('Error: $e');
    }
  }
}

9.7 实操案例:发送 GET/POST 请求

目标

创建一个应用,实现用户列表的获取和用户注册功能。

步骤 1:创建 API 服务类

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

class ApiService {
  final Dio _dio;

  ApiService() : _dio = Dio(
    BaseOptions(
      baseUrl: 'https://jsonplaceholder.typicode.com',
      connectTimeout: Duration(seconds: 10),
      receiveTimeout: Duration(seconds: 10),
    ),
  ) {
    // 添加拦截器
    _dio.interceptors.add(
      InterceptorsWrapper(
        onRequest: (options, handler) {
          print('Request: ${options.uri}');
          return handler.next(options);
        },
        onResponse: (response, handler) {
          print('Response: ${response.statusCode}');
          return handler.next(response);
        },
        onError: (error, handler) {
          print('Error: ${error.message}');
          return handler.next(error);
        },
      ),
    );
  }

  // 获取用户列表
  Future<List<User>> getUsers() async {
    final response = await _dio.get('/users');
    final List<dynamic> data = response.data;
    return data.map((json) => User.fromJson(json)).toList();
  }

  // 获取单个用户
  Future<User> getUser(int id) async {
    final response = await _dio.get('/users/$id');
    return User.fromJson(response.data);
  }

  // 注册用户
  Future<User> registerUser(Map<String, dynamic> userData) async {
    final response = await _dio.post('/users', data: userData);
    return User.fromJson(response.data);
  }
}

步骤 2:创建用户模型

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

part 'user.g.dart';

@JsonSerializable()
class User {
  final int id;
  final String name;
  final String username;
  final String email;
  final Address address;
  final String phone;
  final String website;
  final Company company;

  User({
    required this.id,
    required this.name,
    required this.username,
    required this.email,
    required this.address,
    required this.phone,
    required this.website,
    required this.company,
  });

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

@JsonSerializable()
class Address {
  final String street;
  final String suite;
  final String city;
  final String zipcode;
  final Geo geo;

  Address({
    required this.street,
    required this.suite,
    required this.city,
    required this.zipcode,
    required this.geo,
  });

  factory Address.fromJson(Map<String, dynamic> json) => _$AddressFromJson(json);
  Map<String, dynamic> toJson() => _$AddressToJson(this);
}

@JsonSerializable()
class Geo {
  final String lat;
  final String lng;

  Geo({required this.lat, required this.lng});

  factory Geo.fromJson(Map<String, dynamic> json) => _$GeoFromJson(json);
  Map<String, dynamic> toJson() => _$GeoToJson(this);
}

@JsonSerializable()
class Company {
  final String name;
  final String catchPhrase;
  final String bs;

  Company({
    required this.name,
    required this.catchPhrase,
    required this.bs,
  });

  factory Company.fromJson(Map<String, dynamic> json) => _$CompanyFromJson(json);
  Map<String, dynamic> toJson() => _$CompanyToJson(this);
}

步骤 3:生成模型代码

bash
flutter pub run build_runner build

步骤 4:创建页面

UsersPage.dart

dart
import 'package:flutter/material.dart';
import 'api_service.dart';
import 'user.dart';

class UsersPage extends StatefulWidget {
  const UsersPage({super.key});

  @override
  State<UsersPage> createState() => _UsersPageState();
}

class _UsersPageState extends State<UsersPage> {
  final ApiService _apiService = ApiService();
  List<User> _users = [];
  bool _isLoading = false;
  String? _error;

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

  Future<void> _fetchUsers() async {
    setState(() {
      _isLoading = true;
      _error = null;
    });

    try {
      final users = await _apiService.getUsers();
      setState(() {
        _users = users;
      });
    } catch (e) {
      setState(() {
        _error = 'Failed to fetch users: $e';
      });
    } finally {
      setState(() {
        _isLoading = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Users')),
      body: _isLoading
          ? const Center(child: CircularProgressIndicator())
          : _error != null
              ? Center(child: Text(_error!))
              : ListView.builder(
                  itemCount: _users.length,
                  itemBuilder: (context, index) {
                    final user = _users[index];
                    return ListTile(
                      title: Text(user.name),
                      subtitle: Text(user.email),
                      onTap: () {
                        Navigator.push(
                          context,
                          MaterialPageRoute(
                            builder: (context) => UserDetailPage(userId: user.id),
                          ),
                        );
                      },
                    );
                  },
                ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          Navigator.push(
            context,
            MaterialPageRoute(builder: (context) => const RegisterPage()),
          );
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}

UserDetailPage.dart

dart
import 'package:flutter/material.dart';
import 'api_service.dart';
import 'user.dart';

class UserDetailPage extends StatefulWidget {
  final int userId;

  const UserDetailPage({super.key, required this.userId});

  @override
  State<UserDetailPage> createState() => _UserDetailPageState();
}

class _UserDetailPageState extends State<UserDetailPage> {
  final ApiService _apiService = ApiService();
  User? _user;
  bool _isLoading = false;
  String? _error;

  @override
  void initState() {
    super.initState();
    _fetchUser();
  }

  Future<void> _fetchUser() async {
    setState(() {
      _isLoading = true;
      _error = null;
    });

    try {
      final user = await _apiService.getUser(widget.userId);
      setState(() {
        _user = user;
      });
    } catch (e) {
      setState(() {
        _error = 'Failed to fetch user: $e';
      });
    } finally {
      setState(() {
        _isLoading = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('User Detail')),
      body: _isLoading
          ? const Center(child: CircularProgressIndicator())
          : _error != null
              ? Center(child: Text(_error!))
              : _user != null
                  ? Padding(
                      padding: const EdgeInsets.all(16.0),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Text('Name: ${_user!.name}', style: const TextStyle(fontSize: 18)),
                          const SizedBox(height: 8),
                          Text('Username: ${_user!.username}', style: const TextStyle(fontSize: 16)),
                          const SizedBox(height: 8),
                          Text('Email: ${_user!.email}', style: const TextStyle(fontSize: 16)),
                          const SizedBox(height: 8),
                          Text('Phone: ${_user!.phone}', style: const TextStyle(fontSize: 16)),
                          const SizedBox(height: 8),
                          Text('Website: ${_user!.website}', style: const TextStyle(fontSize: 16)),
                          const SizedBox(height: 16),
                          const Text('Address:', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                          const SizedBox(height: 8),
                          Text('${_user!.address.street}, ${_user!.address.suite}', style: const TextStyle(fontSize: 16)),
                          Text('${_user!.address.city}, ${_user!.address.zipcode}', style: const TextStyle(fontSize: 16)),
                          const SizedBox(height: 16),
                          const Text('Company:', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                          const SizedBox(height: 8),
                          Text(_user!.company.name, style: const TextStyle(fontSize: 16)),
                          Text(_user!.company.catchPhrase, style: const TextStyle(fontSize: 16)),
                        ],
                      ),
                    )
                  : const Center(child: Text('User not found')),
    );
  }
}

RegisterPage.dart

dart
import 'package:flutter/material.dart';
import 'api_service.dart';
import 'user.dart';

class RegisterPage extends StatefulWidget {
  const RegisterPage({super.key});

  @override
  State<RegisterPage> createState() => _RegisterPageState();
}

class _RegisterPageState extends State<RegisterPage> {
  final ApiService _apiService = ApiService();
  final _formKey = GlobalKey<FormState>();
  final _nameController = TextEditingController();
  final _emailController = TextEditingController();
  bool _isLoading = false;
  String? _error;
  User? _registeredUser;

  @override
  void dispose() {
    _nameController.dispose();
    _emailController.dispose();
    super.dispose();
  }

  Future<void> _registerUser() async {
    if (!_formKey.currentState!.validate()) return;

    setState(() {
      _isLoading = true;
      _error = null;
      _registeredUser = null;
    });

    try {
      final userData = {
        'name': _nameController.text,
        'email': _emailController.text,
        'username': _nameController.text.toLowerCase().replaceAll(' ', ''),
        'phone': '123-456-7890',
        'website': 'example.com',
        'address': {
          'street': '123 Main St',
          'suite': 'Apt 1',
          'city': 'New York',
          'zipcode': '10001',
          'geo': {
            'lat': '40.7128',
            'lng': '-74.0060',
          },
        },
        'company': {
          'name': 'Example Company',
          'catchPhrase': 'Great products',
          'bs': 'IT services',
        },
      };

      final user = await _apiService.registerUser(userData);
      setState(() {
        _registeredUser = user;
      });
    } catch (e) {
      setState(() {
        _error = 'Failed to register user: $e';
      });
    } finally {
      setState(() {
        _isLoading = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Register User')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: _isLoading
            ? const Center(child: CircularProgressIndicator())
            : _registeredUser != null
                ? Column(
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: [
                      const Icon(Icons.check_circle, color: Colors.green, size: 64),
                      const SizedBox(height: 16),
                      const Text('User registered successfully!', style: TextStyle(fontSize: 18)),
                      const SizedBox(height: 16),
                      Text('Name: ${_registeredUser!.name}', style: const TextStyle(fontSize: 16)),
                      Text('Email: ${_registeredUser!.email}', style: const TextStyle(fontSize: 16)),
                      const SizedBox(height: 16),
                      ElevatedButton(
                        onPressed: () {
                          Navigator.pop(context);
                        },
                        child: const Text('Back'),
                      ),
                    ],
                  )
                : Form(
                    key: _formKey,
                    child: Column(
                      children: [
                        if (_error != null)
                          Text(_error!, style: const TextStyle(color: Colors.red)),
                        TextFormField(
                          controller: _nameController,
                          decoration: const InputDecoration(labelText: 'Name'),
                          validator: (value) {
                            if (value == null || value.isEmpty) {
                              return 'Please enter your name';
                            }
                            return null;
                          },
                        ),
                        TextFormField(
                          controller: _emailController,
                          decoration: const InputDecoration(labelText: 'Email'),
                          validator: (value) {
                            if (value == null || value.isEmpty) {
                              return 'Please enter your email';
                            }
                            if (!RegExp(r'^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$').hasMatch(value)) {
                              return 'Please enter a valid email';
                            }
                            return null;
                          },
                        ),
                        const SizedBox(height: 20),
                        ElevatedButton(
                          onPressed: _registerUser,
                          child: const Text('Register'),
                        ),
                      ],
                    ),
                  ),
      ),
    );
  }
}

步骤 5:配置主应用

dart
import 'package:flutter/material.dart';
import 'pages/users_page.dart';

void main() {
  runApp(
    MaterialApp(
      title: 'Network Demo',
      home: const UsersPage(),
    ),
  );
}

步骤 6:运行应用

  1. 启动模拟器或连接真机
  2. 运行项目
  3. 测试用户列表获取和用户注册功能

9.8 新手易错点

请求地址错误

错误

  • 基础 URL 配置错误
  • 接口路径拼写错误
  • 缺少协议(http/https)

解决方案

  • 仔细检查 API 文档
  • 使用 Postman 等工具测试接口
  • 确保 URL 格式正确

参数格式错误

错误

  • GET 请求参数放在请求体中
  • POST 请求参数放在 URL 中
  • 参数类型与后端要求不匹配

解决方案

  • 按照 API 文档要求传递参数
  • 使用正确的请求方法
  • 检查参数类型和格式

JSON 解析失败

错误

  • JSON 格式错误
  • 模型类与 JSON 结构不匹配
  • 缺少必要字段

解决方案

  • 使用在线 JSON 验证工具检查格式
  • 确保模型类与 JSON 结构一致
  • 处理 nullable 字段

网络权限问题

错误

  • Android 网络权限未配置
  • iOS 网络权限未配置
  • 混合内容(http/https)问题

解决方案

  • 在 AndroidManifest.xml 中添加网络权限
  • 在 Info.plist 中配置网络权限
  • 使用 https 协议

异常处理不当

错误

  • 未捕获网络异常
  • 错误信息不友好
  • 未处理不同类型的错误

解决方案

  • 使用 try-catch 捕获异常
  • 提供友好的错误提示
  • 针对不同错误类型进行处理

性能问题

错误

  • 频繁发送网络请求
  • 未使用缓存
  • 大文件未使用分块上传/下载

解决方案

  • 合理使用缓存
  • 批量处理请求
  • 大文件使用分块传输

9.9 网络请求最佳实践

1. 使用服务层封装

  • 创建 API 服务类封装网络请求
  • 集中管理 API 调用
  • 便于测试和维护

2. 使用拦截器

  • 添加请求/响应拦截器
  • 统一处理认证、日志等
  • 简化代码

3. 错误处理

  • 统一错误处理机制
  • 提供友好的错误提示
  • 区分网络错误和业务错误

4. 数据模型

  • 使用强类型数据模型
  • 自动生成 JSON 解析代码
  • 确保数据类型安全

5. 性能优化

  • 使用缓存减少网络请求
  • 批量处理请求
  • 合理设置超时时间
  • 使用连接池

6. 安全性

  • 使用 HTTPS
  • 避免明文传输敏感信息
  • 合理使用认证机制

9.10 小结

本章介绍了 Flutter 的网络请求,包括网络请求的概念、常用网络请求插件、GET/POST 请求、响应数据解析、网络请求异常处理等内容。网络请求是 Flutter 应用与后端交互的核心,掌握好网络请求对于构建功能完整的应用至关重要。

我们学习了:

  • Dio 插件:功能强大的网络请求库
  • GET 请求:获取数据的常用方法
  • POST 请求:提交数据的常用方法
  • JSON 解析:手动解析和自动生成代码解析
  • 异常处理:捕获和处理网络错误
  • 最佳实践:封装 API 服务、使用拦截器、优化性能

通过实操案例,我们实现了一个包含用户列表获取和用户注册功能的应用,体验了网络请求的完整流程。在实际开发中,你应该根据项目的需求选择合适的网络请求方案,并遵循最佳实践。

至此,我们已经学习了 Flutter 开发的核心内容,包括基础组件、布局、状态管理、路由与导航、网络请求等。这些内容构成了 Flutter 开发的基础,掌握好这些知识,你就可以开始构建自己的 Flutter 应用了。

© 2026 编程马·菜鸟教程 版权所有