告别状态混乱:用dio+Provider打造清爽的Flutter数据流
【免费下载链接】dio 项目地址: https://gitcode.com/gh_mirrors/dio/dio
你是否还在为Flutter应用中的网络请求与状态管理纠缠不清?当用户操作触发API调用,数据返回后如何优雅地更新UI?本文将带你用dio网络库和Provider状态管理,构建一套清晰、可维护的数据流通方案,彻底解决"请求-状态-UI"的联动难题。
现状诊断:传统方案的三大痛点
在Flutter开发中,我们经常看到这样的代码:
// 典型的混乱实现(example_flutter_app/lib/main.dart 简化版)
ElevatedButton(
onPressed: () async {
try {
await dio.get('https://httpbin.org/get').then((r) {
setState(() {
_text = r.data!; // 直接在回调中更新状态
});
});
} catch (e) {
print(e); // 错误处理被忽略
}
},
child: const Text('Request'),
)
这种写法存在三个致命问题:
- 状态蔓延:网络请求与UI状态混杂在Widget中
- 复用困难:相同请求逻辑在多个页面重复编写
- 测试复杂:业务逻辑与UI组件紧耦合
解决方案:dio+Provider架构设计
我们需要将数据流动清晰分离为三层:
这种架构的核心优势在于:
- 关注点分离:网络请求、状态管理、UI渲染各司其职
- 响应式更新:数据变化自动触发UI重建
- 便于测试:各层可独立单元测试
实现步骤:从0到1搭建集成方案
1. 基础配置:dio网络层封装
首先创建专用的网络请求层(参考 example_flutter_app/lib/http.dart):
// lib/services/api_service.dart
import 'package:dio/dio.dart';
class ApiService {
final Dio _dio;
ApiService() : _dio = Dio(BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: const Duration(seconds: 5),
receiveTimeout: const Duration(seconds: 3),
)) {
// 添加拦截器
_dio.interceptors.add(LogInterceptor(responseBody: true));
}
// 封装GET请求
Future<T> get<T>(String path, {Map<String, dynamic>? queryParameters}) async {
try {
final response = await _dio.get<T>(path, queryParameters: queryParameters);
return response.data!;
} catch (e) {
// 统一错误处理
throw _handleError(e);
}
}
// 更多请求方法...
}
2. 状态管理:创建数据模型
定义数据模型和业务逻辑(创建新文件 lib/models/user_model.dart):
class User {
final String id;
final String name;
User({required this.id, required this.name});
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'],
name: json['name'],
);
}
}
3. 核心集成:Provider连接数据与UI
创建Provider来桥接网络层和UI(创建新文件 lib/providers/user_provider.dart):
import 'package:flutter/foundation.dart';
import '../services/api_service.dart';
import '../models/user_model.dart';
class UserProvider with ChangeNotifier {
final ApiService _apiService = ApiService();
User? _user;
bool _isLoading = false;
String? _errorMessage;
// 暴露状态
User? get user => _user;
bool get isLoading => _isLoading;
String? get errorMessage => _errorMessage;
// 加载用户数据
Future<void> fetchUser(String userId) async {
_isLoading = true;
_errorMessage = null;
notifyListeners(); // 通知UI开始加载
try {
final data = await _apiService.get('/users/$userId');
_user = User.fromJson(data);
} catch (e) {
_errorMessage = e.toString();
} finally {
_isLoading = false;
notifyListeners(); // 通知UI加载完成
}
}
}
4. UI实现:响应式界面构建
在Widget中使用Provider消费状态(参考 example_flutter_app/lib/routes/request.dart 改造):
// lib/screens/user_screen.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/user_provider.dart';
class UserScreen extends StatelessWidget {
final String userId;
const UserScreen({super.key, required this.userId});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('用户详情')),
body: ChangeNotifierProvider(
create: (_) => UserProvider()..fetchUser(userId),
child: Consumer<UserProvider>(
builder: (context, provider, child) {
if (provider.isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (provider.errorMessage != null) {
return Center(child: Text('错误: ${provider.errorMessage}'));
}
final user = provider.user;
if (user == null) {
return const Center(child: Text('用户不存在'));
}
return ListView(
padding: const EdgeInsets.all(16),
children: [
Text('ID: ${user.id}'),
Text('姓名: ${user.name}'),
// 更多用户信息...
],
);
},
),
),
);
}
}
高级技巧:优化与扩展
全局状态与局部状态
根据数据共享范围选择合适的Provider:
// main.dart 中配置全局状态
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => AuthProvider()), // 全局状态
// 其他全局状态...
],
child: const MyApp(),
),
);
}
局部状态则在Widget树中就近创建,避免状态提升导致的性能问题。
错误处理与重试机制
增强ApiService的错误处理能力:
// 改进的错误处理
Future<T> _request<T>(Future<T> Function() request) async {
try {
return await request();
} on DioException catch (e) {
if (e.type == DioExceptionType.connectionTimeout) {
throw '网络连接超时,请检查网络';
}
// 更多错误类型处理...
rethrow;
} catch (e) {
throw '请求失败: ${e.toString()}';
}
}
取消请求:资源优化
结合dio的CancelToken实现请求取消:
// 在Provider中添加取消功能
class UserProvider with ChangeNotifier {
final CancelToken _cancelToken = CancelToken();
Future<void> fetchUser(String userId) async {
// ...
try {
final data = await _apiService.get(
'/users/$userId',
cancelToken: _cancelToken, // 添加取消令牌
);
// ...
} catch (e) {
if (e is DioException && e.type == DioExceptionType.cancel) {
debugPrint('请求已取消');
return;
}
// 其他错误处理
}
}
// 组件销毁时取消请求
@override
void dispose() {
_cancelToken.cancel();
super.dispose();
}
}
最佳实践:避免常见陷阱
- 不要在Provider中存储BuildContext
- 复杂状态拆分多个小Provider
- 网络请求结果使用不可变数据类
- 添加请求缓存策略
- 实现请求重试与超时处理
总结与展望
通过dio与Provider的集成,我们构建了一套清晰的数据流动架构:
这种模式不仅解决了状态管理的混乱问题,还为后续功能扩展(如离线支持、数据预加载)打下了基础。建议在实际项目中进一步封装通用的网络请求Provider基类,减少重复代码。
点赞收藏本文,关注作者获取更多Flutter架构实践技巧!下一篇我们将探讨dio拦截器与Provider结合的高级应用场景。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



