Flutter中initState调用JSON接口并跨组件传值失败求助
解决Flutter中initState调用接口后数据无法传递给子Widget的问题
我帮你排查了代码里的问题,主要有几个点导致数据没法正常加载并传递给CurrencyCard:
问题分析
- 变量作用域错误:你在
setState里用var stgBuy = ...重新声明了变量,这会覆盖全局的同名变量,导致全局变量根本没被赋值,子Widget自然拿不到数据。 - 异步数据未处理加载状态:
initState里的接口请求是异步的,build方法会先执行,这时候数据还没返回,子Widget使用的变量都是null,会引发错误。 - 全局变量不规范:用全局变量管理状态容易导致状态混乱,更好的做法是把数据放在State类内部。
修正方案
下面是修改后的完整代码,我会标注关键修改点:
/* My Json Data { "date":"20180515", "currency":{ "dollar":{ "buy":"4,3600", "sell":"4,4600", }, "sterling":{ "buy":"5,9350", "sell":"6,0350", }, "euro":{ "buy":"5,2150", "sell":"5,3150", } } } */ import 'package:flutter/material.dart'; import 'dart:async'; import 'dart:convert'; import 'package:http/http.dart' as http; final Color darkMainGreen = const Color(0xFF689F38); final Color midMainGreen = const Color(0xFF7CB342); final Color lightMainGreen = const Color(0xFF8BC34A); // TODO: 1) MAIN void main() { runApp(new MyApp()); } // TODO: 2) MyApp class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( debugShowCheckedModeBanner: false, title: "MyDepo", home: new MyHomePage(title: "MyDepo"), ); } } // TODO: 3) Statefull MyHomePage Class class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => new _MyHomePageState(); } // TODO: 4) _MyHomePageState Class class _MyHomePageState extends State<MyHomePage> { // --- 关键修改1:把数据变量移到State类内部,替换全局变量 --- Map<String, dynamic> currencyData; String stgBuy; String stgSell; String eurBuy; String eurSell; String usdBuy; String usdSell; // JSON: GET CURRENCY Future<String> getCurrencyJsonData() async { var response = await http.get( Uri.encodeFull("******* myJsonData *********"), headers: {'Accept': 'application/json'}); setState(() { currencyData = json.decode(response.body); // --- 关键修改2:去掉var,直接给类内变量赋值 --- stgBuy = currencyData["currency"]["sterling"]["buy"]; stgSell = currencyData["currency"]["sterling"]["sell"]; eurBuy = currencyData["currency"]["euro"]["buy"]; eurSell = currencyData["currency"]["euro"]["sell"]; usdBuy = currencyData["currency"]["dollar"]["buy"]; usdSell = currencyData["currency"]["dollar"]["sell"]; }); return "Success!"; } // TODO: BUILD WIDGET @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text( "MyDepo", style: TextStyle(fontSize: 18.0, fontWeight: FontWeight.w500), ), backgroundColor: midMainGreen, ), // --- 关键修改3:用FutureBuilder处理异步数据加载 --- body: FutureBuilder( future: getCurrencyJsonData(), builder: (context, snapshot) { // 数据加载中 if (snapshot.connectionState == ConnectionState.waiting) { return Center(child: CircularProgressIndicator()); } // 数据加载出错 else if (snapshot.hasError) { return Center(child: Text("加载失败: ${snapshot.error}")); } // 数据加载完成 else { return new ListView( padding: const EdgeInsets.all(0.0), children: <Widget>[ new Padding(padding: EdgeInsets.fromLTRB(3.0, 4.0, 3.0, 4.0)), // --- 关键修改4:把数据传递给CurrencyCard --- CurrencyCard( stgBuy: stgBuy, stgSell: stgSell, eurBuy: eurBuy, eurSell: eurSell, usdBuy: usdBuy, usdSell: usdSell, ), ], ); } }, ), ); } } // --- 关键修改5:修改CurrencyCard为接收参数的Widget --- class CurrencyCard extends StatelessWidget { final String stgBuy; final String stgSell; final String eurBuy; final String eurSell; final String usdBuy; final String usdSell; CurrencyCard({ required this.stgBuy, required this.stgSell, required this.eurBuy, required this.eurSell, required this.usdBuy, required this.usdSell, }); @override Widget build(BuildContext context) { final redGradient = const LinearGradient( colors: const <Color>[const Color(0xFFBA110E), const Color(0xFFCF3110)], stops: const <double>[0.4, 0.6], begin: Alignment.topRight, end: Alignment.bottomLeft); final blueGraient = const LinearGradient( colors: const <Color>[const Color(0xFF0075D1), const Color(0xFF00A2E3)], stops: const <double>[0.5, 0.7], begin: Alignment.topLeft, end: Alignment.bottomRight); final greenGradient = const LinearGradient(colors: const <Color>[ const Color(0xFF7CB342), const Color(0xFF8BC34A), ], stops: const <double>[ 0.6, 0.8 ], begin: Alignment.bottomRight, end: Alignment.topLeft); return new Container( constraints: const BoxConstraints(maxHeight: 90.0), margin: const EdgeInsets.all(10.0), child: new Align( alignment: Alignment.center, child: new ListView( shrinkWrap: true, scrollDirection: Axis.horizontal, children: <Widget>[ _buildAction( "£ Sterling", "Buy: ", stgBuy, "Sell: ", stgSell, Colors.red, redGradient, AssetImage("assets/images/dollar.png")), _buildAction( "€ Euro", "Buy: ", eurBuy, "Sell: ", eurSell, Colors.blue, blueGraient, AssetImage("assets/images/euro.png")), _buildAction( "\$ Dollar", "Buy: ", usdBuy, "Sell: ", usdSell, Colors.green, greenGradient, AssetImage("assets/images/sterling.png")), ]), ), ); } Widget _buildAction( String title, String titleAlis, String titleAlisValue, String titleSatis, String titleSatisValue, Color color, Gradient gradient, ImageProvider backgroundImage) { return new GestureDetector( child: new Container( margin: const EdgeInsets.only(right: 5.0, left: 5.0), width: 150.0, decoration: new BoxDecoration( color: color, shape: BoxShape.rectangle, borderRadius: new BorderRadius.circular(10.0), boxShadow: <BoxShadow>[ new BoxShadow( color: Colors.black38, blurRadius: 2.0, spreadRadius: 1.0, offset: new Offset(0.0, 1.0)), ], gradient: gradient ), // --- 补充:把文本内容添加到卡片里,不然看不到数据 --- child: Padding( padding: EdgeInsets.all(8.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold)), SizedBox(height: 4.0), Text("$titleAlis $titleAlisValue", style: TextStyle(color: Colors.white)), Text("$titleSatis $titleSatisValue", style: TextStyle(color: Colors.white)), ], ), ), ), ); } }
修改说明
- 变量作用域修正:把原来的全局变量移到
_MyHomePageState类内部,并且在setState里直接赋值,不再用var重新声明。 - FutureBuilder处理异步:用
FutureBuilder包裹页面内容,这样可以在数据加载时显示加载指示器,加载完成后再渲染包含数据的子Widget,确保build时数据已经准备好。 - 子Widget接收参数:修改
CurrencyCard为接收参数的Widget,通过构造函数把数据传递进去,避免依赖全局变量,状态管理更清晰。 - 补充卡片内容:原来的
_buildAction里没有添加文本内容,我补充了这部分,这样数据能显示在卡片上。
这样修改后,就能确保接口数据加载完成后再渲染子Widget,并且数据能正确传递和显示了。
内容的提问来源于stack exchange,提问作者user9239214
相关产品推荐
相关产品推荐

