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

Flutter Bloc对接OpenWeatherAPI报WeatherErrorstate问题排查

Flutter Bloc状态管理问题排查

你一直触发WeatherErrorstate是以下几个问题叠加导致的,逐个修复即可:

  • 初始请求参数为空:应用启动时输入框无内容,你在初始状态分支直接取textEditingController.text作为请求参数,空城市名会让OpenWeather接口直接返回错误状态码,repository层判断非200就返回null,最终抛出错误状态。
  • 请求URL拼接错误:你写的接口地址末尾多了无效的#字符,会干扰接口参数解析,即使传了正确城市名也可能请求失败。
  • 生命周期逻辑错误:在BlocBuilder的builder方法内直接add事件,会触发「状态更新→builder重建→再次add事件」的死循环,重复请求会打乱正常状态流转。
  • 实例创建冲突:你在homepage的build方法里手动new了Repository实例,和main.dart中BlocProvider注入的实例不匹配,容易出现依赖不一致问题。
  • 全局Controller风险:你把TextEditingController定义成了全局变量,没有和页面生命周期绑定,会造成内存泄漏。
修复方案

1. 修正网络层代码

打开networkfile.dart,删掉URL末尾多余的#,增加空参数拦截,加调试日志方便定位接口错误:

import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:weatherapi/Model/weathermodel.dart';

class Repository {
  Future<Weather?> getdata({required String location}) async {
    // 空参数直接拦截,不发无效请求
    if (location.trim().isEmpty) return null;
    // 移除末尾无效的#符号
    final url = Uri.parse(
        'https://api.openweathermap.org/data/2.5/weather?q=${location.trim()}&appid=c1b66d13f980552b4cdd1123f5c20c72'
    );

    final response = await http.get(url);
    if (response.statusCode != 200) {
      // 调试时打开这行日志,能直接看到接口返回的具体错误原因
      // print('接口报错:${response.statusCode} ${response.body}');
      return null;
    }
    final decodedData = jsonDecode(response.body);
    return Weather.fromJson(decodedData);
  }
}

2. 修正页面逻辑

打开homepage.dart,把Controller放到State内部管理,初始请求放到initState生命周期,不要在builder里触发事件,增加查询触发按钮:

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:weatherapi/Bloc/weather_bloc.dart';
import 'package:weatherapi/Model/weathermodel.dart';

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  // 把Controller放到State内部,绑定页面生命周期
  final TextEditingController textEditingController = TextEditingController();

  @override
  void initState() {
    super.initState();
    // 初始请求默认城市,不要传空字符串
    context.read<WeatherBloc>().add(
      loadWeatherDataEvent(location: 'beijing')
    );
  }

  @override
  void dispose() {
    textEditingController.dispose();
    super.dispose();
  }

  // 统一的查询触发方法
  void _searchWeather() {
    final keyword = textEditingController.text.trim();
    if (keyword.isEmpty) return;
    context.read<WeatherBloc>().add(loadWeatherDataEvent(location: keyword));
  }

  @override
  Widget build(BuildContext context) {
    // 删除build方法内手动创建Repository的代码,统一用BlocProvider注入的实例
    return MaterialApp(
      home: Scaffold(
        body: BlocBuilder<WeatherBloc, WeatherState>(
          builder: (context, state) {
            if (state is WeatherLoadingstate) {
              return const Center(
                child: CircularProgressIndicator(color: Colors.yellow),
              );
            }
            if (state is Weatherloadedstate) {
              return _buildWeatherPage(state.weathermodel);
            }
            if (state is WeatherErrorstate) {
              return Center(
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    const Text('天气查询失败,请检查城市名或网络连接'),
                    const SizedBox(height: 16),
                    ElevatedButton(
                      onPressed: _searchWeather,
                      child: const Text('重试')
                    )
                  ],
                ),
              );
            }
            // 初始状态显示加载指示器即可,不要在这里触发事件
            return const Center(
              child: CircularProgressIndicator(color: Colors.red),
            );
          },
        )
      ),
    );
  }

  Widget _buildWeatherPage(Weather weather) {
    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        children: [
          TextField(
            controller: textEditingController,
            onSubmitted: (_) => _searchWeather(),
            decoration: InputDecoration(
              hintText: '输入城市名',
              suffixIcon: IconButton(
                icon: const Icon(Icons.search),
                onPressed: _searchWeather,
              ),
              border: OutlineInputBorder(
                borderRadius: BorderRadius.circular(30),
              ),
            ),
          ),
          const SizedBox(height: 24),
          Text(
            weather.cityname.toString(),
            style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
          )
        ],
      ),
    );
  }
}

3. 检查依赖注入配置

确认main.dart中Bloc和Repository的注入逻辑正确,不要在页面内手动创建相关实例:

void main() {
  runApp(
    MultiRepositoryProvider(
      providers: [
        RepositoryProvider(create: (context) => Repository()),
      ],
      child: MultiBlocProvider(
        providers: [
          BlocProvider(
            create: (context) => WeatherBloc(
              repository: context.read<Repository>()
            ),
          ),
        ],
        child: const MyApp(),
      ),
    )
  );
}

额外注意

如果改完代码仍报错,先打开networkfile.dart里被注释的日志打印,运行后看控制台输出的接口状态码和返回信息,常见的其他报错原因:

  • 安卓没有在AndroidManifest.xml中配置网络权限
  • iOS没有在Info.plist中配置允许非HTTPS请求
  • 输入的城市名拼写错误,接口无法匹配
  • OpenWeather的appid失效或超出调用配额

内容的提问来源于stack exchange,提问作者Diljith

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 04:01:18