学习内容:
安装 Dio 依赖(
pubspec.yaml配置)Dio 基础:
get请求(获取数据)、post请求(提交数据)实践任务:
用 Dio 调用公开 API(如 “https://jsonplaceholder.typicode.com/posts”)
获取数据后,用
ListView显示接口返回的 “标题列表”
一、Dio简介与环境准备※
1. 什么是Dio※
Dio是一个强大的Dart HTTP请求库,支持RESTful API、FormData、拦截器、请求取消、超时设置等几乎所有网络请求场景。它比Flutter内置的http包功能更丰富,是社区最受欢迎的网络请求工具之一。
2. 安装Dio依赖※
在pubspec.yaml文件中添加dio依赖:
dependencies: flutter: sdk: flutter # 添加 dio 依赖 dio: ^5.4.0 # 请使用 pub.dev 上的最新版本在终端执行命令安装依赖:
flutter pub get在需要使用的文件中导入:
import 'package:dio/dio.dart';
二、Dio基础:GET请求※
1. GET请求基本用法※
// 创建 Dio 实例
final dio = Dio();
void fetchData() async {
try {
// 发起 GET 请求
final response = await dio.get('https://jsonplaceholder.typicode.com/posts');
// 请求成功(状态码 200-299)
print('状态码: ${response.statusCode}');
print('响应数据: ${response.data}');
} catch (e) {
// 请求失败
print('请求失败: $e');
}
}2. 处理JSON数据※
通常API返回JSON格式,需要将其转换为Dart对象
// 定义数据模型
class Post {
final int userId;
final int id;
final String title;
final String body;
Post({
required this.userId,
required this.id,
required this.title,
required this.body,
});
// 从 JSON 映射到对象
factory Post.fromJson(Map<String, dynamic> json) {
return Post(
userId: json['userId'],
id: json['id'],
title: json['title'],
body: json['body'],
);
}
}
// 解析 JSON 数据
List<Post> parsePosts(List<dynamic> responseData) {
return responseData.map((json) => Post.fromJson(json)).toList();
}3. 在Widget中使用网络数据※
结合FutureBuilder处理异步数据
class PostListPage extends StatefulWidget {
const PostListPage({super.key});
@override
State<PostListPage> createState() => _PostListPageState();
}
class _PostListPageState extends State<PostListPage> {
final Dio _dio = Dio();
late Future<List<Post>> _futurePosts;
@override
void initState() {
super.initState();
_futurePosts = _fetchPosts();
}
Future<List<Post>> _fetchPosts() async {
try {
final response = await _dio.get('https://jsonplaceholder.typicode.com/posts');
if (response.statusCode == 200) {
List<dynamic> data = response.data;
return data.map((json) => Post.fromJson(json)).toList();
} else {
throw Exception('请求失败');
}
} catch (e) {
throw Exception('网络错误: $e');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('文章列表')),
body: FutureBuilder<List<Post>>(
future: _futurePosts,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text('错误: ${snapshot.error}'));
} else if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
Post post = snapshot.data![index];
return ListTile(
title: Text(post.title),
subtitle: Text('用户ID: ${post.userId}'),
);
},
);
} else {
return const Center(child: Text('没有数据'));
}
},
),
);
}
}三、Dio基础:POST请求※
1. POST请求基本用法※
void createPost() async {
try {
final response = await dio.post(
'https://jsonplaceholder.typicode.com/posts',
data: {
'title': 'Flutter Dio Post 请求',
'body': '这是一个测试内容',
'userId': 1,
},
);
print('状态码:${response.ststusCode}');
print('创建成功:${response.data}');
} catch (e) {
print('请求失败:$e');
}
}2. 设置请求头※
void postWithHeaders() async {
try {
final response = await dio.post(
'https://jsonplaceholder.typicode.com/posts',
data: {'title': 'test'},
options: Options(
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token',
}
)
);
print(response.data);
} catch (e) {
print(e);
}
}四、综合实战※
- 任务需求
- 使用Dio调用https://jsonplaceholder.typicode.com/posts 获取文章数据。
- 将返回的JSON数据解析为Dart对象列表。
- 使用ListView.builder展示文章标题列表。
- 添加加载状态和错误处理。
代码示例
import 'package:flutter/material.dart'; import 'package:dio/dio.dart'; void main() => runApp(const App()); class App extends StatelessWidget{ const App({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Dio实践', theme: ThemeData(primarySwatch: Colors.yellow), home: const PostListPage(), ); } } class Post { final int userId; final int id; final String title; final String body; Post({ required this.userId, required this.id, required this.title, required this.body }); factory Post.fromJson(Map<String, dynamic> json) { return Post(userId: json['userId'], id: json['id'], title: json['title'], body: json['body']); } } class PostListPage extends StatefulWidget{ const PostListPage({super.key}); @override State<PostListPage> createState() { return _PostListPageState(); } } class _PostListPageState extends State<PostListPage>{ final Dio _dio = Dio(); late Future<List<Post>> _futurePosts; @override void initState() { super.initState(); _futurePosts = _fetchPosts(); } Future<List<Post>> _fetchPosts() async { try { final response = await _dio.get('https://jsonplaceholder.typicode.com/posts'); if (200 == response.statusCode) { List<dynamic> data = response.data; return data.map((json) => Post.fromJson(json)).toList(); } else { throw Exception('请求失败,状态码:${response.statusCode}。'); } } catch (e) { throw Exception('网络错误:$e。'); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('文章标题列表'), centerTitle: true, ), body: FutureBuilder<List<Post>>( future: _futurePosts, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center( child: CircularProgressIndicator(), ); } else if (snapshot.hasError) { return Center( child: Text( '加载失败:${snapshot.error}。', style: const TextStyle(color: Colors.red), ), ); } else if (snapshot.hasData) { return ListView.builder( itemCount: snapshot.data!.length, itemBuilder: (context, index) { Post post = snapshot.data![index]; return ListTile( title: Text( post.title, style: const TextStyle(fontSize: 16), maxLines: 1, overflow: TextOverflow.ellipsis, ), subtitle: Text( '作者ID:${post.userId}', style: TextStyle(color: Colors.grey[600]), ), leading: CircleAvatar( child: Text('${post.id}'), ), onTap: () { // 先关闭当前正在显示的 SnackBar(如果有),否则不会立马显示新的SnackBar ScaffoldMessenger.of(context).hideCurrentSnackBar(); // 再显示新的 SnackBar ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('你点击了第${index + 1}篇文章')), ); }, ); }, ); } else { return const Center(child: Text('没有数据'),); } }, ), ); } }- 补充说明
- ScaffoldMessenger 的 showSnackBar 方法有个默认行为:如果当前已经有 SnackBar 在显示,新调用的 showSnackBar 不会替换它,而是把新 SnackBar 加入队列,等前一个执行完(消失)再执行。
解决方法
要实现 “快速点击时,新提示直接替换旧提示”,只需在 showSnackBar 前,先调用 hideCurrentSnackBar() 关闭当前显示的 SnackBar 即可。
onTap: () { // 关键:先关闭当前正在显示的 SnackBar(如果有) ScaffoldMessenger.of(context).hideCurrentSnackBar(); // 再显示新的 SnackBar ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('你点击了第 ${index + 1} 篇文章'), duration: const Duration(milliseconds: 1000), // 可选:缩短显示时间(默认2秒) ), ); }可选优化
SnackBar( content: Text('你点击了第 ${index + 1} 篇文章'), duration: const Duration(milliseconds: 800), // 800毫秒后自动消失 behavior: SnackBarBehavior.floating, // 可选:悬浮样式(默认是底部嵌入) )
运行效果

五、常见问题与注意事项※
1. 网络权限※
Android:在AndroidMainfest.xml中添加
<uses-permission android:name="android.permission.INTERNET"/>iOS:在Info.plist中添加
NSAppTransportSecurity -> NSAllowsArbitraryLoads = YES(如果使用 HTTP)
2. 异步处理※
网络请求是异步操作,必须使用FutureBuilder或Consumer(结合状态管理)来更新UI。
3. 数据模型※
建议使用json_serializable等工具自动生成JSON解析代码,提高效率和准确性。
4. 状态管理※
在复杂应用中,建议将网络请求逻辑放在状态管理(如:Provider,Bloc)中,而不是直接在Widget中处理。