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
相关产品推荐
相关产品推荐

