一、引言:为什么网络能力是 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 开发者的必经之路。

本文提供的新闻列表项目可直接运行,建议你:

  1. 替换为真实 API(如新闻、天气)
  2. 增加分页、搜索功能
  3. 集成状态管理(如 Provider)实现刷新

💬 互动提问:你在 Flutter 网络请求中遇到过哪些坑?欢迎评论区交流!
❤️ 如果本文对你有帮助,请点赞、收藏、转发支持原创!

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐