Flutter中API网络连接错误的UI反馈实现求助
解决Flutter API网络异常捕获与弹窗联动问题
核心思路
在API调用层抛出明确异常,在StatefulWidget的业务逻辑层捕获异常并触发弹窗,同时结合FutureBuilder处理UI状态(避免在builder中重复弹窗)。
步骤1:改造API调用方法,抛出明确异常
在独立的API文件中,捕获网络相关异常并抛出自定义提示信息,方便上层识别处理:
import 'dart:convert'; import 'package:http/http.dart' as http; import 'dart:io'; Future<WeatherInfo> getInfoWeatherLocation(String location) async { try { final uri = Uri.parse('https://your-weather-api.com/$location'); final response = await http.get(uri); if (response.statusCode != 200) { throw HttpException('请求失败,状态码: ${response.statusCode}'); } return WeatherInfo.fromJson(json.decode(response.body)); } on SocketException { // 捕获无网络连接异常 throw Exception('网络连接失败,请检查网络设置'); } on HttpException catch (e) { // 捕获HTTP错误(如404、500) throw Exception(e.message); } catch (e) { // 兜底处理其他未知异常 throw Exception('获取天气信息失败: ${e.toString()}'); } } // 示例天气数据模型 class WeatherInfo { final String city; final double temp; WeatherInfo({required this.city, required this.temp}); factory WeatherInfo.fromJson(Map<String, dynamic> json) { return WeatherInfo( city: json['city'], temp: json['temp'].toDouble(), ); } }
步骤2:在State类中捕获异常并触发弹窗
在StatefulWidget的业务方法中调用API,用try/catch捕获异常,通过showDialog弹出提示框:
import 'package:flutter/material.dart'; class WeatherSearchPage extends StatefulWidget { const WeatherSearchPage({super.key}); @override State<WeatherSearchPage> createState() => _WeatherSearchPageState(); } class _WeatherSearchPageState extends State<WeatherSearchPage> { final TextEditingController _locationController = TextEditingController(); WeatherInfo? _weatherInfo; bool _isLoading = false; void _superSearchInfo() async { final location = _locationController.text.trim(); if (location.isEmpty) return; setState(() { _isLoading = true; }); try { final weatherData = await getInfoWeatherLocation(location); setState(() { _weatherInfo = weatherData; _isLoading = false; }); } catch (e) { setState(() { _isLoading = false; }); // 触发错误弹窗 showDialog( context: context, barrierDismissible: false, builder: (ctx) => AlertDialog( title: const Text('错误提示'), content: Text(e.toString()), actions: [ TextButton( onPressed: () => Navigator.of(ctx).pop(), child: const Text('确定'), ), ], ), ); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('天气搜索')), body: Padding( padding: const EdgeInsets.all(16), child: Column( children: [ TextField( controller: _locationController, decoration: const InputDecoration(hintText: '输入城市名'), ), const SizedBox(height: 16), ElevatedButton( onPressed: _isLoading ? null : _superSearchInfo, child: _isLoading ? const CircularProgressIndicator(color: Colors.white) : const Text('搜索'), ), if (_weatherInfo != null) Padding( padding: const EdgeInsets.only(top: 20), child: Text( '${_weatherInfo!.city}: ${_weatherInfo!.temp}°C', style: const TextStyle(fontSize: 20), ), ), ], ), ), ); } }
步骤3:结合FutureBuilder的正确姿势(可选)
如果你的页面使用FutureBuilder构建UI,不要在builder中触发弹窗(会因状态重复刷新导致多次弹窗),而是在初始化Future时通过catchError处理异常:
class _WeatherSearchPageState extends State<WeatherSearchPage> { final TextEditingController _locationController = TextEditingController(); Future<WeatherInfo>? _weatherFuture; void _onSearch() { final location = _locationController.text.trim(); if (location.isEmpty) return; setState(() { _weatherFuture = getInfoWeatherLocation(location).catchError((e) { // 在这里捕获异常并弹窗 showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('错误提示'), content: Text(e.toString()), actions: [ TextButton( onPressed: () => Navigator.of(ctx).pop(), child: const Text('确定'), ), ], ), ); // 抛出异常让FutureBuilder识别失败状态 throw e; }); }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('天气搜索')), body: Padding( padding: const EdgeInsets.all(16), child: Column( children: [ TextField(controller: _locationController), ElevatedButton(onPressed: _onSearch, child: const Text('搜索')), Expanded( child: FutureBuilder<WeatherInfo>( future: _weatherFuture, builder: (ctx, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } else if (snapshot.hasError) { // 仅显示静态错误文本,弹窗已在catchError中处理 return const Center(child: Text('加载失败,请重试')); } else if (snapshot.hasData) { return Center( child: Text( '${snapshot.data!.city}: ${snapshot.data!.temp}°C', style: const TextStyle(fontSize: 24), ), ); } else { return const Center(child: Text('请输入城市搜索')); } }, ), ), ], ), ), ); } }
关键注意点
- 确保调用
showDialog时上下文context可用,避免在initState直接调用(需用WidgetsBinding.instance.addPostFrameCallback延迟执行) - 不要在FutureBuilder的builder方法中触发弹窗,该方法会随状态变化多次执行,导致重复弹窗
- 用
setState管理加载状态,避免用户重复点击按钮
内容的提问来源于stack exchange,提问作者juan f garcia
相关产品推荐
相关产品推荐

