1. Flutter 网络请求实战指南
作为一名从2018年开始使用Flutter的开发者,我见证了Flutter网络请求生态的完整演进过程。在实际商业项目中,网络请求是每个Flutter开发者必须掌握的硬核技能。本文将带你从零开始,构建一个完整的网络请求解决方案。
2. 基础环境准备
2.1 依赖库选型考量
在Flutter中,http和dio是最常用的两个网络请求库:
- http:官方维护的轻量级库,适合简单请求场景
- dio:功能强大的第三方库,支持拦截器、文件上传等高级特性
对于新手,我建议先掌握http的基本用法,再过渡到dio。这符合学习曲线规律,也能更好理解网络请求的核心原理。
2.2 依赖配置实战
在pubspec.yaml中添加依赖时,需要注意版本兼容性问题。以下是经过生产环境验证的稳定版本配置:
yaml复制dependencies:
http: ^1.1.0 # 基础网络请求
dio: ^5.0.0 # 高级网络请求
提示:执行
flutter pub get后,建议运行flutter pub outdated检查依赖更新,但不要盲目升级到最新版,避免兼容性问题。
3. HTTP基础请求实现
3.1 GET请求完整实现
下面是一个完整的GET请求示例,包含错误处理和类型转换:
dart复制import 'package:http/http.dart' as http;
import 'dart:convert';
Future<List<Post>> fetchPosts() async {
try {
final response = await http.get(
Uri.parse('https://jsonplaceholder.typicode.com/posts'),
headers: {'Accept': 'application/json'},
).timeout(const Duration(seconds: 10));
if (response.statusCode == 200) {
final List<dynamic> body = json.decode(response.body);
return body.map((json) => Post.fromJson(json)).toList();
} else {
throw HttpException('请求失败: ${response.statusCode}');
}
} on SocketException catch (e) {
throw HttpException('网络连接失败: $e');
} on TimeoutException {
throw HttpException('请求超时');
} on FormatException {
throw HttpException('数据解析错误');
}
}
class Post {
final int id;
final String title;
final String body;
Post({required this.id, required this.title, required this.body});
factory Post.fromJson(Map<String, dynamic> json) {
return Post(
id: json['id'],
title: json['title'],
body: json['body'],
);
}
}
关键点解析:
- 添加了超时控制(10秒)
- 完善了各种异常情况的处理
- 使用强类型模型(Post)替代dynamic
- 包含标准的JSON转换逻辑
3.2 状态管理与UI更新
在StatefulWidget中管理网络请求状态的最佳实践:
dart复制class NetworkListPage extends StatefulWidget {
@override
_NetworkListPageState createState() => _NetworkListPageState();
}
class _NetworkListPageState extends State<NetworkListPage> {
List<Post> _posts = [];
bool _isLoading = true;
String? _errorMessage;
@override
void initState() {
super.initState();
_loadInitialData();
}
Future<void> _loadInitialData() async {
try {
final posts = await fetchPosts();
setState(() {
_posts = posts;
_isLoading = false;
_errorMessage = null;
});
} catch (e) {
setState(() {
_isLoading = false;
_errorMessage = e.toString();
});
_showErrorSnackbar(e.toString());
}
}
void _showErrorSnackbar(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: Colors.red,
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('网络列表')),
body: _buildBody(),
);
}
Widget _buildBody() {
if (_isLoading) {
return Center(child: CircularProgressIndicator());
}
if (_errorMessage != null) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('加载失败', style: TextStyle(color: Colors.red)),
SizedBox(height: 16),
ElevatedButton(
onPressed: _loadInitialData,
child: Text('重试'),
),
],
),
);
}
return RefreshIndicator(
onRefresh: _loadInitialData,
child: ListView.builder(
itemCount: _posts.length,
itemBuilder: (context, index) {
final post = _posts[index];
return Card(
margin: EdgeInsets.symmetric(vertical: 4, horizontal: 8),
child: ListTile(
title: Text(post.title),
subtitle: Text(post.body),
dense: true,
),
);
},
),
);
}
}
改进点:
- 增加了错误状态显示
- 添加了重试按钮
- 改进了列表项UI设计
- 分离了不同状态的构建逻辑
4. 高级DIO实战
4.1 DIO基础配置
Dio的强大之处在于其可配置性。以下是一个生产环境可用的Dio实例配置:
dart复制final dio = Dio(BaseOptions(
baseUrl: 'https://jsonplaceholder.typicode.com',
connectTimeout: 8000,
receiveTimeout: 5000,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
));
// 添加拦截器
dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) {
print('请求: ${options.uri}');
return handler.next(options);
},
onResponse: (response, handler) {
print('响应: ${response.statusCode}');
return handler.next(response);
},
onError: (DioError e, handler) {
print('错误: ${e.message}');
return handler.next(e);
},
));
4.2 使用Dio实现GET请求
dart复制Future<List<Post>> fetchPostsDio() async {
try {
final response = await dio.get('/posts');
if (response.statusCode == 200) {
final List<dynamic> body = response.data;
return body.map((json) => Post.fromJson(json)).toList();
}
throw DioError(
requestOptions: response.requestOptions,
response: response,
);
} on DioError catch (e) {
if (e.response != null) {
throw HttpException('服务器错误: ${e.response?.statusCode}');
} else {
throw HttpException('网络错误: ${e.message}');
}
}
}
Dio优势:
- 自动JSON解析
- 内置错误类型
- 支持请求取消
- 拦截器机制
5. 性能优化与最佳实践
5.1 请求缓存策略
dart复制Future<List<Post>> fetchPostsWithCache() async {
const cacheKey = 'posts_cache';
final cache = await Hive.openBox('network_cache');
try {
final response = await dio.get('/posts');
if (response.statusCode == 200) {
await cache.put(cacheKey, response.data);
return _parsePosts(response.data);
}
} catch (e) {
if (cache.containsKey(cacheKey)) {
return _parsePosts(cache.get(cacheKey));
}
rethrow;
}
throw HttpException('请求失败');
}
5.2 分页加载实现
dart复制class PaginatedListPage extends StatefulWidget {
@override
_PaginatedListPageState createState() => _PaginatedListPageState();
}
class _PaginatedListPageState extends State<PaginatedListPage> {
final List<Post> _posts = [];
int _page = 1;
bool _isLoading = false;
bool _hasMore = true;
Future<void> _loadMore() async {
if (_isLoading || !_hasMore) return;
setState(() => _isLoading = true);
try {
final newPosts = await fetchPosts(page: _page);
setState(() {
_posts.addAll(newPosts);
_isLoading = false;
_page++;
_hasMore = newPosts.isNotEmpty;
});
} catch (e) {
setState(() => _isLoading = false);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('加载失败: $e')),
);
}
}
@override
Widget build(BuildContext context) {
return NotificationListener<ScrollNotification>(
onNotification: (notification) {
if (notification.metrics.pixels ==
notification.metrics.maxScrollExtent) {
_loadMore();
}
return false;
},
child: ListView.builder(
itemCount: _posts.length + (_hasMore ? 1 : 0),
itemBuilder: (context, index) {
if (index == _posts.length) {
return Center(child: CircularProgressIndicator());
}
final post = _posts[index];
return ListTile(title: Text(post.title));
},
),
);
}
}
6. 常见问题解决方案
6.1 跨域问题处理
在开发阶段可能会遇到跨域问题,解决方案:
- 后端配置CORS
- 开发时使用代理:
dart复制(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
(client) {
client.findProxy = (uri) => "PROXY localhost:8888";
client.badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
};
6.2 证书验证问题
生产环境必须严格处理证书验证,开发环境可以临时关闭:
dart复制(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
(client) {
client.badCertificateCallback =
(X509Certificate cert, String host, int port) {
if (kDebugMode) {
return true; // 仅开发环境允许
}
return cert.pem == trustedCertificate; // 生产环境严格验证
};
return client;
};
6.3 文件上传示例
dart复制Future<void> uploadFile(File file) async {
final formData = FormData.fromMap({
'file': await MultipartFile.fromFile(file.path),
'description': 'Uploaded file',
});
try {
final response = await dio.post('/upload', data: formData);
if (response.statusCode == 200) {
print('上传成功');
}
} on DioError catch (e) {
print('上传失败: ${e.message}');
}
}
7. 项目实战建议
- 网络层封装:建议将网络请求封装成独立Service类
- 状态管理:复杂项目考虑使用Provider/Riverpod等状态管理方案
- 错误统一处理:建立全局错误处理机制
- 日志记录:关键请求添加详细日志
- Mock数据:开发阶段使用Mock数据加速开发
我在实际项目中发现,良好的网络层设计可以显著提高应用稳定性和开发效率。建议初期就建立完善的网络请求架构,避免后期重构。