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

Flutter:FutureBuilder未完成时依赖变量Widget报错及预取变量咨询

这个问题我太熟悉了——本质是Flutter的Widget构建顺序导致的:你的Column会同时构建它的两个子Container,而第二个Container里的GridView在Future还没返回数据时就开始执行了,这时候word还是空列表、length也没被赋值,调用word.length或者word.removeAt()自然会触发空指针错误。

下面给你两种实用的解决方案,按需选择:

方案一:把依赖数据的Widget全部放到FutureBuilder内部

最简单的思路是,让需要用到word和length的GridView,只有在数据加载完成后才被构建。你可以把原来Column的两个子Widget合并到FutureBuilder的hasData分支里:

class YourPage extends StatefulWidget {
  @override
  _YourPageState createState() => _YourPageState();
}

class _YourPageState extends State<YourPage> {
  late Future<Quote> _quoteFuture;
  final rng = Random();

  @override
  void initState() {
    super.initState();
    // 关键:在initState里初始化future,避免每次build重复发起网络请求
    _quoteFuture = getQuote();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: FutureBuilder<Quote>(
        future: _quoteFuture,
        builder: (BuildContext context, AsyncSnapshot<Quote> snapshot) {
          if (snapshot.hasData) {
            // 数据到手后再处理word和length
            List<int> word = snapshot.data!.tag.runes.toList();
            word.shuffle();
            int length = word.length;
            
            // 构建完整的布局
            return Column(children: [
              Container(
                child: Center(
                  child: Column(
                    children: <Widget>[
                      Text(snapshot.data!.tag),
                      Image.network('${snapshot.data!.url}'),
                      // 在这里添加作者信息的Widget
                    ],
                  ),
                ),
              ),
              Container(
                height:75,
                child: GridView.count(
                  crossAxisCount: 5,
                  children: List.generate(5, (int index) {
                    return Center(
                      child: FlatButton(
                        child: Text(
                          String.fromCharCode(
                            (length - word.length) < 3 ? word.removeAt(rng.nextInt(word.length)) : 65 + rng.nextInt(25)
                          ),
                          // 补充你的文本样式
                        ),
                        onPressed: () {},
                      ),
                    );
                  })
                )
              )
            ]);
          } else if (snapshot.hasError) {
            // 别忘了处理请求失败的情况
            return Center(child: Text('加载失败: ${snapshot.error}'));
          } else {
            // 加载中显示进度条
            return const Center(child: CircularProgressIndicator());
          }
        },
      ),
    );
  }

  Future<Quote> getQuote() async {
    var API_KEY = '12892554-73418a404928f2a4e42121633';
    final object = generateNoun().take(1);
    String url = "https://pixabay.com/api/?key="+API_KEY+"&q="+Uri.encodeFull(object.toString());
    final response = await http.get(Uri.parse(url)); // 注意转成Uri类型
    if (response.statusCode == 200) {
      return Quote.fromJson(json.decode(response.body));
    } else {
      throw Exception('Failed to load quote');
    }
  }
}

方案二:用状态变量管理数据,分阶段构建Widget

如果不想把整个布局都塞到FutureBuilder里,可以把word和length变成StatefulWidget的状态变量,在数据加载完成后更新状态触发重建:

class YourPage extends StatefulWidget {
  @override
  _YourPageState createState() => _YourPageState();
}

class _YourPageState extends State<YourPage> {
  late Future<Quote> _quoteFuture;
  List<int> word = [];
  int? length; // 用可空类型标记初始状态
  final rng = Random();

  @override
  void initState() {
    super.initState();
    _quoteFuture = getQuote().then((quote) {
      // 数据加载完成后更新状态
      setState(() {
        word = quote.tag.runes.toList();
        word.shuffle();
        length = word.length;
      });
      return quote;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(children: [
        Container(
          child: FutureBuilder<Quote>(
            future: _quoteFuture,
            builder: (context, snapshot) {
              if (snapshot.hasData) {
                return Center(
                  child: Column(
                    children: <Widget>[
                      Text(snapshot.data!.tag),
                      Image.network('${snapshot.data!.url}'),
                    ],
                  ),
                );
              } else if (snapshot.hasError) {
                return Center(child: Text('加载失败'));
              } else {
                return const CircularProgressIndicator();
              }
            },
          ),
        ),
        // 只有length不为null时才构建GridView,否则显示占位符保持布局高度
        length == null 
            ? const SizedBox(height: 75)
            : Container(
                height:75,
                child: GridView.count(
                  crossAxisCount: 5,
                  children: List.generate(5, (int index) {
                    return Center(
                      child: FlatButton(
                        child: Text(
                          String.fromCharCode(
                            (length! - word.length) < 3 ? word.removeAt(rng.nextInt(word.length)) : 65 + rng.nextInt(25)
                          ),
                        ),
                        onPressed: () {},
                      ),
                    );
                  })
                )
              )
      ]),
    );
  }

  // getQuote函数同方案一,记得处理错误
  Future<Quote> getQuote() async {
    var API_KEY = '12892554-73418a404928f2a4e42121633';
    final object = generateNoun().take(1);
    String url = "https://pixabay.com/api/?key="+API_KEY+"&q="+Uri.encodeFull(object.toString());
    final response = await http.get(Uri.parse(url));
    if (response.statusCode == 200) {
      return Quote.fromJson(json.decode(response.body));
    } else {
      throw Exception('Failed to load quote');
    }
  }
}

额外提醒

  • 绝对不要在build方法里直接调用getQuote(),否则每次Widget重建都会重复发起网络请求,把Future放到initState里是标准做法。
  • 一定要处理Future的错误状态,避免用户看到无限加载的进度条。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 08:54:13