You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 18:52:40