告别状态混乱:用dio+Provider打造清爽的Flutter数据流

告别状态混乱:用dio+Provider打造清爽的Flutter数据流

【免费下载链接】dio 【免费下载链接】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架构设计

我们需要将数据流动清晰分离为三层:

mermaid

这种架构的核心优势在于:

  • 关注点分离:网络请求、状态管理、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();
  }
}

最佳实践:避免常见陷阱

  1. 不要在Provider中存储BuildContext
  2. 复杂状态拆分多个小Provider
  3. 网络请求结果使用不可变数据类
  4. 添加请求缓存策略
  5. 实现请求重试与超时处理

总结与展望

通过dio与Provider的集成,我们构建了一套清晰的数据流动架构:

mermaid

这种模式不仅解决了状态管理的混乱问题,还为后续功能扩展(如离线支持、数据预加载)打下了基础。建议在实际项目中进一步封装通用的网络请求Provider基类,减少重复代码。

点赞收藏本文,关注作者获取更多Flutter架构实践技巧!下一篇我们将探讨dio拦截器与Provider结合的高级应用场景。

【免费下载链接】dio 【免费下载链接】dio 项目地址: https://gitcode.com/gh_mirrors/dio/dio

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值