Flutter 网络请求与数据解析全指南:从 http 到 Dio + json_serializable 实战
一、引言:为什么网络能力是 Flutter 应用的“生命线”?
几乎所有的现代移动应用都依赖远程数据——无论是用户信息、商品列表、新闻资讯还是实时消息。而 网络请求(HTTP/HTTPS) 正是连接 App 与后端服务的桥梁。
在 Flutter 中,虽然 UI 构建能力强大,但如何高效、安全、可维护地获取和解析数据,才是构建生产级应用的关键。
本文将带你深入 Flutter 网络编程的核心:
- 使用官方
http包发起请求 - 进阶使用
Dio实现拦截器、超时、重试等企业级功能 - 自动化 JSON 解析(告别手写
fromJson) - 结合
FutureBuilder或Provider展示加载/错误/成功状态 - 最终实现一个 可运行的新闻列表 App
二、基础方案:使用官方 http 包
Flutter 官方提供了轻量级的 http 包,适合简单场景。
2.1 添加依赖
1dependencies:
2 flutter:
3 sdk: flutter
4 http: ^1.2.0
2.2 发起 GET 请求(示例)
1import 'package:http/http.dart' as http;
2import 'dart:convert';
3
4Future<void> fetchPosts() async {
5 final response = await http.get(
6 Uri.parse('https://jsonplaceholder.typicode.com/posts'),
7 );
8
9 if (response.statusCode == 200) {
10 final List<dynamic> data = jsonDecode(response.body);
11 print('共获取 ${data.length} 条帖子');
12 } else {
13 throw Exception('请求失败: ${response.statusCode}');
14 }
15}
✅ 优点:轻量、无额外依赖
❌ 缺点:无拦截器、无自动重试、错误处理繁琐
三、进阶方案:使用 Dio —— Flutter 网络请求的事实标准
Dio 是由 Flutter 社区广泛采用的 HTTP 客户端,功能强大且高度可定制。
3.1 为什么选择 Dio?
| 功能 | Dio 支持 | http 包 |
|---|---|---|
| 拦截器(Interceptor) | ✅ | ❌ |
| 超时控制 | ✅ | 需手动实现 |
| 自动重试 | ✅(通过拦截器) | ❌ |
| FormData(上传文件) | ✅ | ❌ |
| 取消请求 | ✅ | ❌ |
| 响应类型泛型 | ✅ | ❌ |
📌 在中大型项目中,强烈推荐使用 Dio
3.2 集成 Dio
1dependencies:
2 dio: ^5.7.0
3.3 创建全局 Dio 实例(带拦截器)
1// services/api_client.dart
2import 'package:dio/dio.dart';
3
4class ApiClient {
5 static final Dio _dio = Dio();
6
7 static Dio get instance {
8 _dio.options.baseUrl = 'https://jsonplaceholder.typicode.com/';
9 _dio.options.connectTimeout = 10000; // 10s
10 _dio.options.receiveTimeout = 10000;
11
12 // 请求拦截器(如添加 Token)
13 _dio.interceptors.add(InterceptorsWrapper(
14 onRequest: (options, handler) {
15 options.headers['Authorization'] = 'Bearer your_token';
16 print('【请求】${options.method} ${options.path}');
17 return handler.next(options);
18 },
19 onResponse: (response, handler) {
20 print('【响应】${response.statusCode}');
21 return handler.next(response);
22 },
23 onError: (DioException e, handler) {
24 print('【错误】${e.message}');
25 return handler.next(e);
26 },
27 ));
28
29 return _dio;
30 }
31}
💡 单例模式确保全局复用,避免重复创建
四、JSON 解析:从手写 to 自动生成
网络返回的 JSON 数据需转换为 Dart 对象。手动编写 fromJson 易出错且冗长。
4.1 手动解析(不推荐)
1class Post {
2 final int id;
3 final String title;
4 final String body;
5
6 Post({required this.id, required this.title, required this.body});
7
8 factory Post.fromJson(Map<String, dynamic> json) {
9 return Post(
10 id: json['id'],
11 title: json['title'],
12 body: json['body'],
13 );
14 }
15}
4.2 使用 json_serializable 自动生成(推荐!)
步骤 1:添加依赖
1dependencies:
2 json_annotation: ^4.9.0
3
4dev_dependencies:
5 build_runner: ^2.4.9
6 json_serializable: ^6.8.0
步骤 2:定义模型类
1// models/post.dart
2import 'package:json_annotation/json_annotation.dart';
3
4part 'post.g.dart'; // 自动生成文件
5
6@JsonSerializable()
7class Post {
8 final int id;
9 final String title;
10 final String body;
11
12 Post({required this.id, required this.title, required this.body});
13
14 factory Post.fromJson(Map<String, dynamic> json) => _$PostFromJson(json);
15 Map<String, dynamic> toJson() => _$PostToJson(this);
16}
步骤 3:生成代码
终端运行:
1flutter pub run build_runner build --delete-conflicting-outputs
✅ 自动生成
post.g.dart,包含完整的序列化逻辑
🔒 类型安全,避免拼写错误
五、实战项目:新闻列表 App(完整代码)
我们将调用 JSONPlaceholder 的 /posts 接口,展示一个带加载状态的新闻列表。
5.1 项目结构
1lib/
2├── main.dart
3├── models/
4│ └── post.dart
5├── services/
6│ ├── api_client.dart
7│ └── post_service.dart
8└── widgets/
9 └── post_list_widget.dart
5.2 PostService(业务层)
1// services/post_service.dart
2import 'package:dio/dio.dart';
3import '../models/post.dart';
4import 'api_client.dart';
5
6class PostService {
7 final Dio _dio = ApiClient.instance;
8
9 Future<List<Post>> getPosts() async {
10 try {
11 final response = await _dio.get('/posts');
12 final List<dynamic> list = response.data;
13 return list.map((e) => Post.fromJson(e)).toList();
14 } catch (e) {
15 rethrow;
16 }
17 }
18}
5.3 UI 层:使用 FutureBuilder
1// widgets/post_list_widget.dart
2import 'package:flutter/material.dart';
3import '../services/post_service.dart';
4import '../models/post.dart';
5
6class PostListWidget extends StatelessWidget {
7 final PostService _postService = PostService();
8
9 @override
10 Widget build(BuildContext context) {
11 return Scaffold(
12 appBar: AppBar(title: Text('新闻列表')),
13 body: FutureBuilder<List<Post>>(
14 future: _postService.getPosts(),
15 builder: (context, snapshot) {
16 if (snapshot.connectionState == ConnectionState.waiting) {
17 return Center(child: CircularProgressIndicator());
18 } else if (snapshot.hasError) {
19 return Center(child: Text('加载失败: ${snapshot.error}'));
20 } else if (!snapshot.hasData || snapshot.data!.isEmpty) {
21 return Center(child: Text('暂无数据'));
22 } else {
23 final posts = snapshot.data!;
24 return ListView.builder(
25 itemCount: posts.length,
26 itemBuilder: (context, index) {
27 return Card(
28 margin: EdgeInsets.all(8),
29 child: Padding(
30 padding: EdgeInsets.all(12),
31 child: Column(
32 crossAxisAlignment: CrossAxisAlignment.start,
33 children: [
34 Text(
35 posts[index].title,
36 style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
37 maxLines: 2,
38 overflow: TextOverflow.ellipsis,
39 ),
40 SizedBox(height: 8),
41 Text(
42 posts[index].body,
43 maxLines: 3,
44 overflow: TextOverflow.ellipsis,
45 ),
46 ],
47 ),
48 ),
49 );
50 },
51 );
52 }
53 },
54 ),
55 );
56 }
57}
5.4 主入口
1// main.dart
2import 'package:flutter/material.dart';
3import 'widgets/post_list_widget.dart';
4
5void main() => runApp(MyApp());
6
7class MyApp extends StatelessWidget {
8 @override
9 Widget build(BuildContext context) {
10 return MaterialApp(
11 title: 'Flutter 网络请求实战',
12 theme: ThemeData(primarySwatch: Colors.blue),
13 home: PostListWidget(),
14 );
15 }
16}
✅ 运行效果:启动后显示加载圈 → 成功加载新闻卡片 → 支持错误提示
六、错误处理与用户体验优化
6.1 常见错误类型
- 网络不可达(
DioExceptionType.connectionError) - 超时(
DioExceptionType.timeout) - 服务器错误(500)
- JSON 解析失败
6.2 统一错误处理(在拦截器中)
1onError: (DioException e, handler) {
2 String message = '未知错误';
3 if (e.type == DioExceptionType.connectionError) {
4 message = '网络连接失败,请检查网络';
5 } else if (e.type == DioExceptionType.timeout) {
6 message = '请求超时';
7 } else if (e.response?.statusCode == 401) {
8 message = '登录已过期';
9 }
10 // 可在此弹出 Toast 或跳转登录页
11 print('【统一错误】$message');
12 return handler.next(e);
13}
6.3 加载状态最佳实践
- 使用
CircularProgressIndicator()表示加载中 - 提供“重试”按钮(
ElevatedButton+ 重新调用 future) - 骨架屏(Skeleton)提升视觉体验(可用
shimmer插件)
七、性能与安全建议
7.1 性能优化
- 避免在 build 中发起请求 → 放在
initState或FutureBuilder - 使用缓存:对不变数据(如配置)使用
shared_preferences或内存缓存 - 分页加载:大数据列表用
ListView.builder+ 分页 API
7.2 安全建议
- 不要硬编码 Token → 使用安全存储(如
flutter_secure_storage) - HTTPS 强制启用(Android/iOS 默认要求)
- 敏感接口加签验签
八、替代方案对比
| 方案 | 适用场景 | 学习成本 |
|---|---|---|
http |
小型 Demo、简单请求 | 低 |
Dio |
中大型项目(推荐) | 中 |
Retrofit for Dart |
喜欢注解风格(类似 Java) | 中高 |
Riverpod + AsyncNotifier |
状态管理集成 | 高 |
📌 对于 90% 的项目,Dio + json_serializable + Provider/Riverpod 是黄金组合。
九、结语
网络请求是 Flutter 应用与世界对话的窗口。掌握 Dio 的拦截器机制、自动化 JSON 解析 和 优雅的错误处理,是你迈向专业 Flutter 开发者的必经之路。
本文提供的新闻列表项目可直接运行,建议你:
- 替换为真实 API(如新闻、天气)
- 增加分页、搜索功能
- 集成状态管理(如 Provider)实现刷新
💬 互动提问:你在 Flutter 网络请求中遇到过哪些坑?欢迎评论区交流!
❤️ 如果本文对你有帮助,请点赞、收藏、转发支持原创!
更多推荐



所有评论(0)