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

Flutter Home页搜索城市后数值未更新 setState异常问题

问题定位

你的页面数据不更新是多个逻辑问题共同导致的,核心问题如下:

  • getData方法上多余了错误的@override注解,且posts变量的赋值操作写在了setState外部,异步场景下容易出现状态和UI同步异常
  • 发起新搜索请求前没有重置加载状态,也没有做输入合法性校验,空输入、错误城市名导致请求失败时,不会触发UI更新,旧数据会一直残留
  • 缺少异常捕获和请求资源释放逻辑,接口报错时会静默失败,没有任何用户提示
  • 气压值没有做边界处理,数值超出合法范围时会打断环形进度条的渲染流程
  • 缺少初始化生命周期调用,首次进入页面不会自动加载默认城市数据
修复方案
  1. 移除getData上错误的@override注解,重写initState生命周期,首次进入页面加载默认城市天气
  2. 所有和UI相关的状态赋值操作全部放到setState回调内部执行
  3. 发起请求前先将isLoaded置为false,展示加载状态,请求结束(无论成功失败)再恢复
  4. 增加输入非空校验,请求过程捕获异常,失败时给用户提示
  5. 对气压值做边界裁剪,保证环形进度条的percent值始终在0~1区间
  6. 请求结束后及时关闭http客户端,释放资源
修复后完整代码

HomePage修复代码

import 'package:flutter/material.dart';
import 'package:glass_kit/glass_kit.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:percent_indicator/percent_indicator.dart';

import '../model/model.dart';
import '../services/remote_services.dart';
class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  Post? posts;
  bool isLoaded = true;
  double? pressure;
  final TextEditingController city = TextEditingController();
  // 首次加载默认城市,可自行修改
  final String defaultCity = "Beijing";

  @override
  void initState() {
    super.initState();
    // 首次进入页面加载默认城市天气
    getData(defaultCity);
  }

  Future<void> getData(String searchCity) async {
    // 空输入直接拦截
    if(searchCity.trim().isEmpty) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text("请输入城市名称"))
      );
      return;
    }
    // 请求开始前切换加载状态
    setState(() {
      isLoaded = false;
    });
    try {
      Post? newPosts = await RemoteService().getPosts(searchCity);
      if(newPosts != null) {
        // 所有UI相关状态更新全部放在setState内部
        setState(() {
          posts = newPosts;
          pressure = posts?.main.pressure;
          isLoaded = true;
        });
      } else {
        // 接口返回非200状态(城市不存在、参数错误等)
        setState(() {
          isLoaded = true;
        });
        if(mounted) {
          ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(content: Text("未查询到该城市天气,请检查城市名称"))
          );
        }
      }
    } catch (e) {
      // 捕获网络异常、解析异常
      setState(() {
        isLoaded = true;
      });
      if(mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text("网络异常,请稍后重试"))
        );
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        height: MediaQuery.of(context).size.height,
        width: MediaQuery.of(context).size.width,
        decoration: const BoxDecoration(
          image: DecorationImage(
            image: AssetImage("assets/images/Clouds.jpg"),
            fit: BoxFit.cover,
          ),
        ),
        child: SafeArea(
          child: SingleChildScrollView(
            child: Visibility(
              visible: isLoaded,
              // 加载状态展示loading组件
              replacement: const Center(
                child: Padding(
                  padding: EdgeInsets.all(100),
                  child: CircularProgressIndicator(color: Colors.white,),
                ),
              ),
              child: Padding(
                padding: const EdgeInsets.all(8.0),
                child: Column(
                  children: [
                    Row(
                      children: [
                        GlassContainer.clearGlass(
                          borderWidth: 0,
                          height: 50,
                          width: MediaQuery.of(context).size.width-70,
                          borderRadius: BorderRadius.circular(10),
                          child: TextField(
                            controller: city,
                            textAlign: TextAlign.center,
                            style: const TextStyle(
                                color: Colors.white
                            ),
                            decoration: const InputDecoration(
                              hintText: "Enter City",
                              hintStyle: TextStyle(color: Colors.white70),
                              border: InputBorder.none,
                            ),
                            // 支持键盘回车直接触发搜索
                            onSubmitted: (value) {
                              getData(city.text.trim());
                            },
                          ),
                        ),
                        const SizedBox(width: 10,),
                        GlassContainer.clearGlass(
                          height: 50,
                          width: 50,
                          borderWidth: 0,
                          borderRadius: BorderRadius.circular(10),
                          child: Center(
                            child: IconButton(
                              onPressed: (){
                                // 自动去除输入首尾空格,避免无效请求
                                getData(city.text.trim());
                              },
                              icon: const Icon(Icons.search,color: Colors.white,),
                            ),
                          ),
                        )
                      ],
                    ),
                    const SizedBox(height: 5,),
                    Row(
                      children: [
                        Column(
                          children: [
                            GlassContainer.clearGlass(
                              height: MediaQuery.of(context).size.height/8.099,
                              width: MediaQuery.of(context).size.width/3.990,
                              borderWidth: 0,
                              borderRadius: BorderRadius.circular(10),
                              blur: 10,
                              child: Column(
                                mainAxisAlignment: MainAxisAlignment.center,
                                children: [
                                  const FaIcon(FontAwesomeIcons.water, color: Colors.white,),
                                  Text(
                                    posts==null?" ":"${posts?.main.humidity}%",
                                    style: const TextStyle(
                                        color: Colors.white,
                                        fontSize: 24
                                    ),
                                  )
                                ],
                              ),
                            ),
                            const SizedBox(height: 5,),
                            GlassContainer.clearGlass(
                              height: MediaQuery.of(context).size.height/8.099,
                              width: MediaQuery.of(context).size.width/3.990,
                              borderWidth: 0,
                              borderRadius: BorderRadius.circular(10),
                              blur: 10,
                              child: Column(
                                mainAxisAlignment: MainAxisAlignment.center,
                                children: [
                                  const FaIcon(FontAwesomeIcons.wind, color: Colors.white,),
                                  Text(
                                    posts==null?" ":"${posts?.wind.speed}",
                                    style: const TextStyle(
                                        color: Colors.white,
                                        fontSize: 24
                                    ),
                                  ),
                                  const Text(
                                    "Km/h",
                                    style: TextStyle(
                                        fontSize: 12,
                                        color: Colors.white
                                    ),
                                  )
                                ],
                              ),
                            )
                          ],
                        ),
                        const Expanded(child: SizedBox()),
                        GlassContainer.clearGlass(
                          width: MediaQuery.of(context).size.width/1.51102941,
                          height: MediaQuery.of(context).size.height/3.77731092-10,
                          borderRadius: BorderRadius.circular(10),
                          borderWidth: 0,
                          child: Center(
                            child: Column(
                              mainAxisAlignment: MainAxisAlignment.center,
                              children: [
                                Text(
                                  posts==null?" ":"${posts?.main.temp} C",
                                  style: const TextStyle(
                                    fontSize: 75,
                                    color: Colors.white,

                                  ),
                                ),
                                const SizedBox(height: 10,),
                                Text(
                                  posts==null?"":"Feels like ${posts?.main.feelsLike} C",
                                  style: const TextStyle(
                                      fontSize: 18,
                                      color: Colors.white
                                  ),
                                )
                              ],
                            ),
                          ),
                        ),
                        const Expanded(child: SizedBox()),

                      ],
                    ),
                    const SizedBox(height: 7,),
                    Row(
                      children: [
                        GlassContainer.clearGlass(
                          width: MediaQuery.of(context).size.width/1.51102941,
                          height: MediaQuery.of(context).size.height/3.77731092-10+12,
                          borderRadius: BorderRadius.circular(10),
                          borderWidth: 0,
                          child: CircularPercentIndicator(
                            radius: 90.0,
                            lineWidth: 13.0,
                            animation: true,
                            // 裁剪percent值到0-1合法区间,避免渲染报错
                            percent: pressure==null?0: (pressure!/1084).clamp(0.0, 1.0),
                            center:  Column(
                              mainAxisAlignment: MainAxisAlignment.center,
                              children: [
                                FaIcon((pressure!=null)?(pressure!>1013)?FontAwesomeIcons.arrowUp:FontAwesomeIcons.arrowDown:FontAwesomeIcons.arrowDown, color: Colors.white,),
                                Text(
                                  "${pressure ?? ''} mB",
                                  style:
                                  const TextStyle(fontWeight: FontWeight.bold, fontSize: 30, color: Colors.white),
                                ),
                              ],
                            ),
                            circularStrokeCap: CircularStrokeCap.round,
                            progressColor: Colors.white,
                          ),

                        ),
                        const Expanded(child: SizedBox()),
                        Column(
                          children: [
                            GlassContainer.clearGlass(
                              height: MediaQuery.of(context).size.height/8.099,
                              width: MediaQuery.of(context).size.width/3.990,
                              borderWidth: 0,
                              borderRadius: BorderRadius.circular(10),
                              blur: 10,
                              child: Column(
                                mainAxisAlignment: MainAxisAlignment.center,
                                children: [
                                  const FaIcon(FontAwesomeIcons.temperatureArrowUp, color: Colors.white,),
                                  Text(
                                    posts==null? " " : "${posts?.main.tempMax} C",
                                    style: const TextStyle(
                                        color: Colors.white,
                                        fontSize: 24
                                    ),
                                  )
                                ],
                              ),
                            ),
                            const SizedBox(height: 18,),
                            GlassContainer.clearGlass(
                              height: MediaQuery.of(context).size.height/8.099,
                              width: MediaQuery.of(context).size.width/3.990,
                              borderWidth: 0,
                              borderRadius: BorderRadius.circular(10),
                              blur: 10,
                              child: Column(
                                mainAxisAlignment: MainAxisAlignment.center,
                                children: [
                                  const FaIcon(FontAwesomeIcons.temperatureArrowDown, color: Colors.white,),
                                  Text(
                                    posts==null? " " : "${posts?.main.tempMin} C",
                                    style: const TextStyle(
                                        color: Colors.white,
                                        fontSize: 24
                                    ),
                                  )
                                ],
                              ),
                            ),
                          ],
                        ),
                        const Expanded(child: SizedBox())

                      ],
                    ),
                  ],
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

RemoteService修复代码

import 'package:http/http.dart' as http;
import '../model/model.dart';

class RemoteService{
  Future <Post?> getPosts(String city) async{
    http.Client client = http.Client();
    try {
      var uri = Uri.parse("https://api.openweathermap.org/data/2.5/weather?q=$city&appid=5e749d88f2df02cacc9be6abf8088531&units=metric");
      var response = await client.get(uri);
      if (response.statusCode == 200){
        var json = response.body;
        return postFromJson(json);
      }
      return null;
    } catch (e) {
      rethrow;
    } finally {
      // 无论请求成功失败都关闭客户端,释放连接资源
      client.close();
    }
  }
}
修复效果
  • 首次进入页面自动加载默认城市天气,点击搜索会先展示加载状态,请求成功后所有天气数据正常刷新
  • 空输入、错误城市名、网络异常都会弹出对应提示,不会出现点击无反应的情况
  • 气压组件做了边界保护,不会因为数值越界打断渲染
  • 支持键盘回车直接触发搜索,输入自动去除首尾空格,减少无效请求

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 20:42:19