如何在Flutter中使用Go Router实现API请求时的加载弹窗?
使用Go Router实现导航前加载对话框
在需要先调用API获取路由路径再跳转的场景下,你可以通过在导航逻辑中嵌入加载状态管理来实现,核心是先触发加载对话框,完成API请求后再执行导航操作。以下是具体实现步骤:
1. 基础Go Router配置
先确保你已经配置好基础的Go Router实例,示例如下:
final GoRouter _router = GoRouter( routes: [ GoRoute( path: '/', builder: (context, state) => const HomeScreen(), ), GoRoute( path: '/details/:id', builder: (context, state) => DetailsScreen(id: state.pathParameters['id']!), ), ], );
2. 实现带加载状态的导航逻辑
在触发导航的按钮或操作中,按以下流程处理:
- 显示加载对话框
- 异步调用API获取目标路由
- 根据API结果执行导航或错误处理
- 关闭加载对话框
示例代码:
ElevatedButton( onPressed: () async { // 1. 显示加载对话框 showDialog( context: context, barrierDismissible: false, // 禁止点击外部关闭 builder: (context) => const Center( child: CircularProgressIndicator(), ), ); try { // 2. 调用API获取路由路径 final String targetRoute = await _fetchRouteFromApi(); // 3. 关闭对话框并跳转 Navigator.of(context).pop(); // 关闭加载框 context.go(targetRoute); // 使用Go Router导航 } catch (e) { // 4. 处理错误:关闭对话框并提示 Navigator.of(context).pop(); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('获取路由失败: $e')), ); } }, child: const Text('跳转至目标页面'), ); // 模拟API请求方法 Future<String> _fetchRouteFromApi() async { await Future.delayed(const Duration(seconds: 2)); // 模拟网络延迟 // 假设API返回的路由是'/details/123' return '/details/123'; }
3. 优化方案:封装通用导航方法
如果多个地方需要类似逻辑,可以封装成通用方法,避免重复代码:
Future<void> navigateWithLoading(BuildContext context, Future<String> Function() fetchRoute) async { showDialog( context: context, barrierDismissible: false, builder: (context) => const Center(child: CircularProgressIndicator()), ); try { final route = await fetchRoute(); Navigator.of(context).pop(); context.go(route); } catch (e) { Navigator.of(context).pop(); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('导航失败: $e')), ); } } // 使用时: ElevatedButton( onPressed: () => navigateWithLoading(context, _fetchRouteFromApi), child: const Text('跳转'), );
4. 进阶:结合状态管理(可选)
如果你的项目使用状态管理库(如Riverpod、Provider),可以将加载状态提升到全局状态,让UI更灵活响应:
- 在状态类中维护
isLoading变量 - 触发导航时设置
isLoading = true,请求完成后设为false - 在UI层根据
isLoading显示加载对话框或正常内容
示例(Riverpod):
final navigationProvider = StateNotifierProvider<NavigationNotifier, bool>((ref) { return NavigationNotifier(); }); class NavigationNotifier extends StateNotifier<bool> { NavigationNotifier() : super(false); Future<void> navigate(BuildContext context) async { state = true; try { final route = await _fetchRouteFromApi(); context.go(route); } catch (e) { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.toString()))); } finally { state = false; } } } // UI层监听状态显示对话框 Consumer( builder: (context, ref, child) { final isLoading = ref.watch(navigationProvider); if (isLoading) { WidgetsBinding.instance.addPostFrameCallback((_) { showDialog( context: context, barrierDismissible: false, builder: (context) => const Center(child: CircularProgressIndicator()), ); }); } else { // 如果对话框存在,关闭它 Navigator.of(context, rootNavigator: true).popUntil((route) => !route.isDialog); } return ElevatedButton( onPressed: () => ref.read(navigationProvider.notifier).navigate(context), child: const Text('跳转'), ); }, )
内容的提问来源于stack exchange,提问作者yadhu
相关产品推荐
相关产品推荐

