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

Dart中如何等待异步函数完成后再执行?Flutter场景咨询

解决Flutter异步获取城市列表后再构建UI的问题

嘿,我来帮你搞定这个异步加载的难题!在Flutter里,直接在build方法里调用异步函数肯定会踩坑——因为build会立刻执行,而你的fetchCitiesList还没来得及拿到城市数据,这时候cities还是空的,要么显示空白,要么直接报错。下面给你两种最常用的靠谱方案:

方案1:用FutureBuilder(官方推荐,更简洁)

FutureBuilder是Flutter专门用来处理异步数据构建UI的组件,它会自动监听异步操作的状态(加载中、成功、失败),帮你自动更新UI,不用手动管理状态。

步骤1:修改异步方法,让它返回Future

先把fetchCitiesList改成返回Future类型,这样FutureBuilder才能监听它:

import 'dart:convert';
import 'dart:io';
import 'dart:async';

Future<List<String>> fetchCitiesList() async {
  var url = 'https://gnsyms.000webhostapp.com/cities.php';
  var httpClient = HttpClient();
  
  try {
    var req = await httpClient.getUrl(Uri.parse(url));
    var res = await req.close();
    
    if (res.statusCode == HttpStatus.OK) {
      // 把响应转换成字符串再解析成JSON
      var jsonString = await res.transform(utf8.decoder).join();
      List<dynamic> jsonList = jsonDecode(jsonString);
      // 假设返回的是字符串类型的城市列表,根据你的实际JSON结构调整
      return jsonList.cast<String>();
    } else {
      throw Exception('加载城市失败,状态码:${res.statusCode}');
    }
  } catch (e) {
    throw Exception('请求错误:$e');
  } finally {
    // 记得关闭HttpClient,避免资源泄漏
    httpClient.close();
  }
}

步骤2:用FutureBuilder包裹列表UI

在build方法里用FutureBuilder替代直接生成列表,根据异步状态显示不同内容:

class CitiesScreen extends StatefulWidget {
  const CitiesScreen({super.key});

  @override
  State<CitiesScreen> createState() => _CitiesScreenState();
}

class _CitiesScreenState extends State<CitiesScreen> {
  late final Future<List<String>> _citiesFuture;

  @override
  void initState() {
    super.initState();
    // 把Future存在State变量里,避免每次build重复发起请求
    _citiesFuture = fetchCitiesList();
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('城市列表')),
      body: FutureBuilder<List<String>>(
        future: _citiesFuture,
        builder: (context, snapshot) {
          // 加载中:显示进度指示器
          if (snapshot.connectionState == ConnectionState.waiting) {
            return const Center(child: CircularProgressIndicator());
          }
          // 请求失败:显示错误信息
          else if (snapshot.hasError) {
            return Center(child: Text('出错了:${snapshot.error}'));
          }
          // 请求成功:生成列表
          else if (snapshot.hasData) {
            var cities = snapshot.data!;
            return ListView.builder(
              itemCount: cities.length,
              itemBuilder: (context, index) {
                return ListTile(title: Text(cities[index]));
              },
            );
          }
          // 没有数据的情况
          else {
            return const Center(child: Text('暂无城市数据'));
          }
        },
      ),
    );
  }
}

方案2:用StatefulWidget + setState(手动管理状态)

如果你更习惯手动控制状态更新,可以用这种方式:在initState里启动异步请求,拿到数据后调用setState触发UI刷新。

完整代码示例

import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';

class CitiesScreen extends StatefulWidget {
  const CitiesScreen({super.key});

  @override
  State<CitiesScreen> createState() => _CitiesScreenState();
}

class _CitiesScreenState extends State<CitiesScreen> {
  List<String>? _cities;
  String? _errorMessage;

  @override
  void initState() {
    super.initState();
    // 在页面初始化时发起请求
    _fetchCitiesList();
  }

  void _fetchCitiesList() async {
    var url = 'https://gnsyms.000webhostapp.com/cities.php';
    var httpClient = HttpClient();
    
    try {
      var req = await httpClient.getUrl(Uri.parse(url));
      var res = await req.close();
      
      if (res.statusCode == HttpStatus.OK) {
        var jsonString = await res.transform(utf8.decoder).join();
        List<dynamic> jsonList = jsonDecode(jsonString);
        // 拿到数据后调用setState,通知Flutter刷新UI
        setState(() {
          _cities = jsonList.cast<String>();
        });
      } else {
        setState(() {
          _errorMessage = '加载失败,状态码:${res.statusCode}';
        });
      }
    } catch (e) {
      setState(() {
        _errorMessage = '请求错误:$e';
      });
    } finally {
      httpClient.close();
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('城市列表')),
      body: _buildBody(),
    );
  }

  Widget _buildBody() {
    // 先处理错误情况
    if (_errorMessage != null) {
      return Center(child: Text(_errorMessage!));
    }
    // 加载中显示进度条
    if (_cities == null) {
      return const Center(child: CircularProgressIndicator());
    }
    // 没有数据的情况
    if (_cities!.isEmpty) {
      return const Center(child: Text('暂无城市数据'));
    }
    // 数据正常,生成列表
    return ListView.builder(
      itemCount: _cities!.length,
      itemBuilder: (context, index) {
        return ListTile(title: Text(_cities![index]));
      },
    );
  }
}

重要提醒

  1. 绝对不要在build方法里直接调用异步函数,否则每次UI刷新都会重复发起请求,浪费网络资源。
  2. 一定要处理网络请求的异常,给用户清晰的错误反馈,避免应用崩溃。
  3. 如果接口返回的是复杂JSON对象(不是简单字符串列表),建议创建对应的模型类来解析数据,比如用json_serializable插件简化解析工作。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 11:18:16