保姆级教程:用Dio和GitCode API v5为你的Flutter应用添加用户与仓库搜索功能
Flutter实战:Dio与GitCode API v5深度整合指南
在移动应用开发领域,数据交互能力直接决定了产品的用户体验质量。Flutter作为跨平台开发框架的佼佼者,配合Dio这一强大的网络请求库,能够为开发者提供高效稳定的API通信解决方案。本文将深入探讨如何利用Dio 5.7.0版本与GitCode API v5构建功能完备的代码仓库搜索系统,涵盖从基础配置到高级功能实现的完整链路。
1. 项目环境配置与Dio初始化
构建稳健的网络请求层是应用开发的基石。我们首先需要为Flutter项目配置必要的依赖并初始化Dio客户端。
在pubspec.yaml中添加以下依赖项:
dependencies: flutter: sdk: flutter dio: ^5.7.0 # 网络请求核心库 cached_network_image: ^3.3.0 # 图片缓存 pull_to_refresh: ^2.0.0 # 下拉刷新 logger: ^1.4.0 # 请求日志创建网络服务单例类,这是保证应用内网络配置一致性的最佳实践:
class NetworkService { static final NetworkService _instance = NetworkService._internal(); factory NetworkService() => _instance; late final Dio dio; NetworkService._internal() { dio = Dio(BaseOptions( baseUrl: 'https://api.gitcode.com/api/v5', connectTimeout: const Duration(seconds: 15), receiveTimeout: const Duration(seconds: 20), )); // 添加日志拦截器 dio.interceptors.add(LogInterceptor( request: true, requestHeader: true, responseBody: true, )); // 添加认证拦截器 dio.interceptors.add(InterceptorsWrapper( onRequest: (options, handler) { final token = AppConfig.demoToken; if (token.isNotEmpty) { options.headers['Authorization'] = 'token $token'; } return handler.next(options); }, onError: (error, handler) async { // 统一错误处理逻辑 return handler.next(error); }, )); } }关键配置说明:
- 基础URL设置为GitCode API v5端点
- 连接超时15秒,接收超时20秒
- 日志拦截器用于调试阶段请求监控
- 认证拦截器自动为需要Token的请求添加授权头
2. API响应模型设计与解析
良好的数据模型是应用稳定性的保障。我们需要为GitCode API定义强类型的Dart模型类。
2.1 用户搜索响应模型
@JsonSerializable() class UserSearchResult { final int totalCount; final List<GitCodeUser> items; UserSearchResult({ required this.totalCount, required this.items, }); factory UserSearchResult.fromJson(Map<String, dynamic> json) => _$UserSearchResultFromJson(json); } @JsonSerializable() class GitCodeUser { final String login; final int id; @JsonKey(name: 'avatar_url') final String avatarUrl; final String type; final double score; GitCodeUser({ required this.login, required this.id, required this.avatarUrl, required this.type, required this.score, }); factory GitCodeUser.fromJson(Map<String, dynamic> json) => _$GitCodeUserFromJson(json); }2.2 仓库搜索响应模型
@JsonSerializable() class RepoSearchResult { final int totalCount; final List<CodeRepository> items; RepoSearchResult({ required this.totalCount, required this.items, }); factory RepoSearchResult.fromJson(Map<String, dynamic> json) => _$RepoSearchResultFromJson(json); } @JsonSerializable() class CodeRepository { final int id; final String name; final String fullName; final User owner; final String htmlUrl; final String? description; final int stargazersCount; CodeRepository({ required this.id, required this.name, required this.fullName, required this.owner, required this.htmlUrl, this.description, required this.stargazersCount, }); factory CodeRepository.fromJson(Map<String, dynamic> json) => _$CodeRepositoryFromJson(json); }模型使用技巧:
- 使用json_serializable自动生成序列化代码
- 通过@JsonKey处理API返回的蛇形命名
- 对可能为null的字段显式声明nullable
3. 搜索功能核心实现
基于配置好的Dio客户端,我们可以实现具体的搜索业务逻辑。
3.1 用户搜索服务
class UserSearchService { final Dio _dio = NetworkService().dio; Future<UserSearchResult> searchUsers({ required String query, int perPage = 20, int page = 1, }) async { try { final response = await _dio.get( '/search/users', queryParameters: { 'q': query, 'per_page': perPage, 'page': page, }, ); return UserSearchResult.fromJson(response.data); } on DioException catch (e) { if (e.response?.statusCode == 403) { throw Exception('API rate limit exceeded'); } rethrow; } } }3.2 仓库搜索服务
class RepoSearchService { final Dio _dio = NetworkService().dio; Future<RepoSearchResult> searchRepositories({ required String query, String sort = 'stars', String order = 'desc', int perPage = 20, int page = 1, }) async { try { final response = await _dio.get( '/search/repositories', queryParameters: { 'q': query, 'sort': sort, 'order': order, 'per_page': perPage, 'page': page, }, ); return RepoSearchResult.fromJson(response.data); } on DioException catch (e) { if (e.response?.statusCode == 422) { throw Exception('Invalid search query'); } rethrow; } } }错误处理最佳实践:
- 捕获DioException处理网络异常
- 针对不同状态码提供有意义的错误提示
- 使用rethrow保留原始堆栈信息
4. 搜索界面与状态管理
将API服务与UI界面结合,需要合理的状态管理方案。
4.1 搜索状态模型
class SearchState { final bool isLoading; final String? error; final List<dynamic> results; final int currentPage; final bool hasMore; SearchState({ this.isLoading = false, this.error, this.results = const [], this.currentPage = 1, this.hasMore = true, }); SearchState copyWith({ bool? isLoading, String? error, List<dynamic>? results, int? currentPage, bool? hasMore, }) { return SearchState( isLoading: isLoading ?? this.isLoading, error: error ?? this.error, results: results ?? this.results, currentPage: currentPage ?? this.currentPage, hasMore: hasMore ?? this.hasMore, ); } }4.2 搜索功能ViewModel
class SearchViewModel extends ChangeNotifier { final UserSearchService _userService = UserSearchService(); final RepoSearchService _repoService = RepoSearchService(); SearchState _state = SearchState(); SearchMode _mode = SearchMode.user; SearchState get state => _state; SearchMode get mode => _mode; void setMode(SearchMode mode) { _mode = mode; _state = SearchState(); // 重置状态 notifyListeners(); } Future<void> search(String query, {bool loadMore = false}) async { if (query.isEmpty) return; try { final nextPage = loadMore ? _state.currentPage + 1 : 1; _state = _state.copyWith( isLoading: true, error: null, currentPage: nextPage, ); notifyListeners(); dynamic result; if (_mode == SearchMode.user) { result = await _userService.searchUsers( query: query, page: nextPage, ); } else { result = await _repoService.searchRepositories( query: query, page: nextPage, ); } _state = _state.copyWith( isLoading: false, results: loadMore ? [..._state.results, ...result.items] : result.items, hasMore: result.items.isNotEmpty, ); } catch (e) { _state = _state.copyWith( isLoading: false, error: e.toString(), ); } notifyListeners(); } }状态管理要点:
- 使用ChangeNotifier实现轻量级状态管理
- 封装copyWith方法简化状态更新
- 区分初始搜索和加载更多操作
- 根据搜索模式调用不同服务
5. 搜索结果展示与优化
良好的用户界面需要精心设计的数据展示方式。
5.1 用户搜索结果项组件
class UserResultItem extends StatelessWidget { final GitCodeUser user; const UserResultItem({super.key, required this.user}); @override Widget build(BuildContext context) { return ListTile( leading: CircleAvatar( backgroundImage: CachedNetworkImageProvider(user.avatarUrl), radius: 24, ), title: Text(user.login), subtitle: Text('Score: ${user.score.toStringAsFixed(1)}'), trailing: const Icon(Icons.chevron_right), onTap: () { // 跳转到用户详情 }, ); } }5.2 仓库搜索结果项组件
class RepoResultItem extends StatelessWidget { final CodeRepository repo; const RepoResultItem({super.key, required this.repo}); @override Widget build(BuildContext context) { return Card( margin: const EdgeInsets.symmetric(vertical: 8), child: Padding( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ CircleAvatar( backgroundImage: CachedNetworkImageProvider(repo.owner.avatarUrl), radius: 16, ), const SizedBox(width: 8), Text( repo.fullName, style: Theme.of(context).textTheme.titleMedium, ), ], ), if (repo.description != null) ...[ const SizedBox(height: 8), Text( repo.description!, maxLines: 2, overflow: TextOverflow.ellipsis, ), ], const SizedBox(height: 12), Row( children: [ const Icon(Icons.star_border, size: 16), const SizedBox(width: 4), Text(repo.stargazersCount.toString()), ], ), ], ), ), ); } }UI优化技巧:
- 使用CachedNetworkImage缓存头像图片
- 为卡片添加合理的边距和内边距
- 对长文本使用ellipsis溢出处理
- 使用主题中的文本样式保持一致性
6. 高级功能实现
提升搜索体验的关键在于细节功能的完善。
6.1 搜索防抖实现
class Debouncer { final Duration delay; Timer? _timer; Debouncer({required this.delay}); void call(void Function() callback) { _timer?.cancel(); _timer = Timer(delay, callback); } void dispose() { _timer?.cancel(); } } // 在搜索页面中使用 final _debouncer = Debouncer(delay: const Duration(milliseconds: 500)); void onSearchTextChanged(String text) { _debouncer(() { if (text.length >= 3) { _viewModel.search(text); } }); } @override void dispose() { _debouncer.dispose(); super.dispose(); }6.2 下拉刷新与加载更多
RefreshController _refreshController = RefreshController(); Widget _buildResultList(SearchViewModel viewModel) { return SmartRefresher( controller: _refreshController, enablePullDown: true, enablePullUp: true, onRefresh: () => viewModel.search(_currentQuery), onLoading: () => viewModel.search(_currentQuery, loadMore: true), child: ListView.builder( itemCount: viewModel.state.results.length, itemBuilder: (context, index) { final item = viewModel.state.results[index]; return viewModel.mode == SearchMode.user ? UserResultItem(user: item) : RepoResultItem(repo: item); }, ), ); }性能优化点:
- 500ms的防抖间隔平衡响应速度与性能
- 最小3字符触发搜索减少无效请求
- 正确管理Timer生命周期防止内存泄漏
- 使用SmartRefresher实现流畅的刷新体验
7. 安全与认证最佳实践
API访问安全是应用开发不可忽视的重要方面。
7.1 Token安全存储方案
class AuthService { static const _tokenKey = 'gitcode_access_token'; final FlutterSecureStorage _storage = const FlutterSecureStorage(); Future<void> saveToken(String token) async { await _storage.write(key: _tokenKey, value: token); NetworkService().updateToken(token); } Future<String?> getToken() async { return await _storage.read(key: _tokenKey); } Future<void> clearToken() async { await _storage.delete(key: _tokenKey); NetworkService().updateToken(''); } } // 在NetworkService中添加更新方法 void updateToken(String newToken) { dio.interceptors.removeWhere((i) => i is InterceptorsWrapper); dio.interceptors.add(InterceptorsWrapper( onRequest: (options, handler) { if (newToken.isNotEmpty) { options.headers['Authorization'] = 'token $newToken'; } return handler.next(options); }, )); }7.2 速率限制处理
class RateLimitInterceptor extends Interceptor { final Dio _dio; DateTime? _lastRequestTime; RateLimitInterceptor(this._dio); @override void onRequest(RequestOptions options, RequestInterceptorHandler handler) { final now = DateTime.now(); if (_lastRequestTime != null && now.difference(_lastRequestTime!) < const Duration(seconds: 1)) { // 添加延迟避免触发速率限制 Timer(const Duration(seconds: 1), () => handler.next(options)); return; } _lastRequestTime = now; handler.next(options); } @override void onError(DioException err, ErrorInterceptorHandler handler) { if (err.response?.statusCode == 403) { // 处理速率限制错误 final resetTime = err.response?.headers.value('x-ratelimit-reset'); if (resetTime != null) { final resetDate = DateTime.fromMillisecondsSinceEpoch(int.parse(resetTime) * 1000); showRateLimitDialog(resetDate); } } handler.next(err); } void showRateLimitDialog(DateTime resetTime) { // 显示友好的速率限制提示 } }安全建议:
- 使用FlutterSecureStorage保存敏感Token
- 实现请求间隔控制避免触发API限制
- 解析速率限制响应头提供准确恢复时间
- 为用户提供清晰的速率限制提示
8. 测试与调试技巧
确保搜索功能稳定可靠需要全面的测试策略。
8.1 网络请求Mock测试
void main() { late UserSearchService service; late Dio mockDio; setUp(() { mockDio = Dio(); service = UserSearchService(mockDio); }); test('successful user search returns parsed results', () async { final mockResponse = { 'total_count': 1, 'items': [ { 'login': 'testuser', 'id': 123, 'avatar_url': 'https://example.com/avatar.jpg', 'type': 'User', 'score': 1.0, } ] }; when(mockDio.get(any, queryParameters: anyNamed('queryParameters'))) .thenAnswer((_) async => Response( data: mockResponse, statusCode: 200, requestOptions: RequestOptions(path: ''), )); final result = await service.searchUsers(query: 'test'); expect(result.totalCount, 1); expect(result.items.first.login, 'testuser'); }); test('handles rate limit error', () async { when(mockDio.get(any, queryParameters: anyNamed('queryParameters'))) .thenThrow(DioException( response: Response( statusCode: 403, requestOptions: RequestOptions(path: ''), ), requestOptions: RequestOptions(path: ''), )); expect( () => service.searchUsers(query: 'test'), throwsA(isA<Exception>().having( (e) => e.toString(), 'message', contains('API rate limit exceeded'), )), ); }); }8.2 集成测试关键场景
void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('full user search flow', (tester) async { // 启动应用 await tester.pumpWidget(const MyApp()); // 导航到搜索页面 await tester.tap(find.byIcon(Icons.search)); await tester.pumpAndSettle(); // 输入搜索词 await tester.enterText(find.byType(TextField), 'flutter'); await tester.pump(const Duration(milliseconds: 600)); // 等待防抖 // 验证结果加载 expect(find.byType(CircularProgressIndicator), findsOneWidget); await tester.pumpAndSettle(); // 验证结果展示 expect(find.byType(UserResultItem), findsWidgets); // 测试加载更多 await tester.fling( find.byType(ListView), const Offset(0, -500), 1000, ); await tester.pumpAndSettle(); expect(find.byType(CircularProgressIndicator), findsOneWidget); await tester.pumpAndSettle(); expect(find.byType(UserResultItem), findsNWidgets(40)); }); }测试覆盖要点:
- 单元测试验证服务层逻辑
- Mock Dio响应测试各种网络场景
- 集成测试完整用户流程
- 特别关注边界条件和错误情况
9. 性能优化与监控
生产环境的应用需要持续的性能优化。
9.1 网络缓存策略
class CacheInterceptor extends Interceptor { final CacheStore _cache; CacheInterceptor(this._cache); @override void onRequest(RequestOptions options, RequestInterceptorHandler handler) async { if (options.method == 'GET') { final cached = await _cache.get(options.uri.toString()); if (cached != null) { return handler.resolve( Response( data: cached, statusCode: 200, requestOptions: options, ), ); } } handler.next(options); } @override void onResponse(Response response, ResponseInterceptorHandler handler) async { if (response.requestOptions.method == 'GET' && response.statusCode == 200) { await _cache.set( response.requestOptions.uri.toString(), response.data, Duration(minutes: 5), ); } handler.next(response); } } abstract class CacheStore { Future<dynamic> get(String key); Future<void> set(String key, dynamic value, Duration duration); }9.2 性能监控集成
class PerformanceInterceptor extends Interceptor { final FirebasePerformance _performance = FirebasePerformance.instance; final Map<String, Trace> _activeTraces = {}; @override void onRequest(RequestOptions options, RequestInterceptorHandler handler) { final trace = _performance.newTrace('api_${options.path}'); trace.start(); _activeTraces[options.uri.toString()] = trace; handler.next(options); } @override void onResponse(Response response, ResponseInterceptorHandler handler) { _endTrace(response.requestOptions.uri.toString(), success: true); handler.next(response); } @override void onError(DioException err, ErrorInterceptorHandler handler) { _endTrace(err.requestOptions.uri.toString(), success: false); handler.next(err); } void _endTrace(String uri, {required bool success}) { final trace = _activeTraces.remove(uri); trace?.setAttribute('success', success.toString()); trace?.stop(); } }优化方向:
- 实现GET请求缓存减少网络流量
- 使用Firebase Performance监控API耗时
- 记录成功失败率分析接口稳定性
- 根据监控数据优化慢请求
10. 国际化与可访问性
让搜索功能面向全球用户需要考虑多语言支持。
10.1 多语言资源文件
# en.yaml search: title: "Code Search" userTab: "Users" repoTab: "Repositories" hint: "Enter keywords..." empty: "No results found" error: "Search failed: {error}" # zh.yaml search: title: "代码搜索" userTab: "用户" repoTab: "仓库" hint: "输入关键词..." empty: "未找到结果" error: "搜索失败: {error}"10.2 可访问搜索组件
class AccessibleSearchBar extends StatelessWidget { const AccessibleSearchBar({super.key}); @override Widget build(BuildContext context) { return Semantics( textField: true, hint: context.l10n.searchHint, child: TextField( decoration: InputDecoration( labelText: context.l10n.searchHint, prefixIcon: const Icon(Icons.search), border: const OutlineInputBorder(), ), ), ); } } class AccessibleResultItem extends StatelessWidget { final GitCodeUser user; const AccessibleResultItem({super.key, required this.user}); @override Widget build(BuildContext context) { return Semantics( label: 'User ${user.login} with score ${user.score}', child: ListTile( leading: ExcludeSemantics( child: CircleAvatar( backgroundImage: CachedNetworkImageProvider(user.avatarUrl), ), ), title: Text(user.login), subtitle: Text('Score: ${user.score}'), ), ); } }国际化要点:
- 使用arb或yaml管理多语言资源
- 通过context.l10n访问本地化字符串
- 为视觉障碍用户添加语义化标签
- 排除纯装饰性元素的语义
11. 持续集成与部署
自动化流程可以显著提升开发效率。
11.1 GitHub Actions工作流
name: Flutter CI on: push: branches: [ main ] pull_request: branches: [ main ] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: subosito/flutter-action@v2 - run: flutter pub get - run: flutter test - run: flutter build apk --release deploy: needs: test runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: subosito/flutter-action@v2 - run: flutter pub get - run: flutter build appbundle --release - uses: r0adkll/upload-google-play@v1 with: serviceAccountJson: ${{ secrets.GOOGLE_PLAY_SA }} packageName: com.example.gitcode_tool releaseFiles: build/app/outputs/bundle/release/app-release.aab track: production11.2 代码质量检查配置
# analysis_options.yaml include: package:flutter_lints/flutter.yaml analyzer: strong-mode: implicit-casts: false implicit-dynamic: false errors: todo: ignore exclude: - '**/*.g.dart' - '**/*.freezed.dart' linter: rules: - always_declare_return_types - avoid_empty_else - avoid_print - cancel_subscriptions - constant_identifier_names - control_flow_in_finally - directives_ordering - empty_catches - empty_constructor_bodies - library_names - library_prefixes - no_duplicate_case_values - null_closures - prefer_final_fields - prefer_interpolation_to_compose_strings - prefer_is_empty - prefer_is_not_empty - slash_for_doc_comments - test_types_in_equals - throw_in_finally - type_init_formals - unnecessary_brace_in_string_interps - unnecessary_const - unnecessary_getters_setters - unnecessary_new - unnecessary_null_in_if_null_operators - unnecessary_overrides - unnecessary_statements - unnecessary_this - use_rethrow_when_possible - use_setters_to_change_properties - use_string_buffers - valid_regexpsCI/CD最佳实践:
- 在PR和main分支推送时运行测试
- 使用官方Flutter Action设置环境
- 构建发布版本并部署到Google Play
- 配置严格的静态代码分析规则
- 使用secrets管理敏感部署凭证
12. 错误监控与日志收集
生产环境的问题诊断需要完善的日志系统。
12.1 Sentry错误监控集成
Future<void> main() async { await SentryFlutter.init( (options) { options.dsn = 'YOUR_DSN_HERE'; options.tracesSampleRate = 0.2; }, appRunner: () => runApp(const MyApp()), ); } class ErrorInterceptor extends Interceptor { @override void onError(DioException err, ErrorInterceptorHandler handler) { Sentry.captureException( err, stackTrace: err.stackTrace, withScope: (scope) { scope.setExtra('url', err.requestOptions.uri.toString()); scope.setExtra('method', err.requestOptions.method); if (err.response != null) { scope.setExtra('status_code', err.response!.statusCode); scope.setExtra('response_body', err.response!.data); } }, ); handler.next(err); } }12.2 结构化日志记录
class ApiLogger { final Logger _logger = Logger( printer: PrettyPrinter( methodCount: 0, errorMethodCount: 5, colors: true, ), ); void logRequest(RequestOptions options) { _logger.i(''' API Request: ${options.method} ${options.uri} Headers: ${options.headers} Query: ${options.queryParameters} '''); } void logResponse(Response response) { _logger.i(''' API Response: ${response.statusCode} ${response.requestOptions.uri} Duration: ${response.requestOptions.extra['duration']}ms Data: ${response.data} '''); } void logError(DioException error) { _logger.e(''' API Error: ${error.type} ${error.requestOptions.uri} ${error.response?.statusCode} ${error.message} Stack: ${error.stackTrace} '''); } }监控策略:
- 使用Sentry捕获并分析生产环境错误
- 设置适当的采样率平衡成本与覆盖
- 记录完整的请求上下文信息
- 开发环境使用彩色日志提高可读性
- 生产环境日志上传到集中式服务
13. 未来功能扩展方向
基于现有搜索功能,可以考虑以下扩展方向:
13.1 高级搜索过滤器
class SearchFilters { final String? language; final int? stars; final int? forks; final DateTime? updatedAfter; const SearchFilters({ this.language, this.stars, this.forks, this.updatedAfter, }); Map<String, String> toQueryParams() { final params = <String, String>{}; if (language != null) params['language'] = language!; if (stars != null) params['stars'] = '>=$stars'; if (forks != null) params['forks'] = '>=$forks'; if (updatedAfter != null) { params['pushed'] = '>=${updatedAfter!.toIso8601String()}'; } return params; } }13.2 搜索历史与收藏
class SearchHistoryService { final Box<String> _box; SearchHistoryService(this._box); List<String> getHistory() => _box.values.toList(); Future<void> addQuery(String query) async { if (query.isEmpty) return; // 移除重复项 await _box.delete(query); // 添加到最前面 await _box.put(query, query); // 限制历史记录数量 if (_box.length > 20) { final keys = _box.keys.toList(); await _box.delete(keys.last); } } Future<void> clear() => _box.clear(); } // 使用Hive初始化 await Hive.openBox<String>('search_history');扩展思路:
- 按语言、星标等条件过滤结果
- 保存用户搜索历史提供快捷访问
- 实现仓库收藏功能
- 添加趋势搜索建议
- 支持高级搜索语法
14. 架构演进与优化
随着功能增加,需要考虑架构的持续优化。
14.1 状态管理升级方案
class SearchState { final String query; final SearchMode mode; final List<dynamic> results; final bool isLoading; final String? error; final int page; final bool hasMore; const SearchState({ this.query = '', this.mode = SearchMode.user, this.results = const [], this.isLoading = false, this.error, this.page = 1, this.hasMore = true, }); SearchState copyWith({ String? query, SearchMode? mode, List<dynamic>? results, bool? isLoading, String? error, int? page, bool? hasMore, }) { return SearchState( query: query ?? this.query, mode: mode ?? this.mode, results: results ?? this.results, isLoading: isLoading ?? this.isLoading, error: error ?? this.error, page: page ?? this.page, hasMore: hasMore ?? this.hasMore, ); } } @riverpod class SearchNotifier extends _$SearchNotifier { @override SearchState build() => const SearchState(); Future<void> search(String query) async { if (query.isEmpty || state.isLoading) return; state = state.copyWith( query: query, isLoading: true, error: null, page: 1, ); try { final result = await _fetchResults(query, 1); state = state.copyWith( isLoading: false, results: result.items, hasMore: result.items.isNotEmpty, ); } catch (e) { state = state.copyWith( isLoading: false, error: e.toString(), ); } } Future<void> loadMore() async { if (state.isLoading || !state.hasMore) return; state = state.copyWith(isLoading: true); try { final result = await _fetchResults( state.query, state.page + 1, ); state = state.copyWith( isLoading: false, results: [...state.results, ...result.items], page: state.page + 1, hasMore: result.items.isNotEmpty, ); } catch (e) { state = state.copyWith( isLoading: false, error: e.toString(), ); } } Future<dynamic> _fetchResults(String query, int page) { // 实际获取结果的逻辑 } }14.2 依赖注入配置
final dioProvider = Provider<Dio>((ref) { final dio = Dio(BaseOptions( baseUrl: 'https://api.gitcode.com/api/v5', )); dio.interceptors.addAll([ LogInterceptor(), CacheInterceptor(ref.read(cacheProvider)), AuthInterceptor(ref.read(authProvider)), ]); return dio; }); final userSearchProvider = Provider<UserSearchService>((ref) { return UserSearchService(ref.read(dioProvider)); }); final repoSearchProvider = Provider<RepoSearchService>((ref) { return RepoSearchService(ref.read(dioProvider)); }); final searchProvider = StateNotifierProvider<SearchNotifier, SearchState>((ref) { return SearchNotifier( userService: ref.read(userSearchProvider), repoService: ref.read(repoSearchProvider), ); });架构演进建议:
- 从简单状态管理逐步过渡到Riverpod/Bloc
- 使用依赖注入提高可测试性
- 按功能模块组织代码结构
- 实现清晰的层级分离(UI-逻辑-服务)
