Flutter中getApiKey方法循环调用的原因及解决办法
问题原因及解决办法
嘿,我来帮你拆解下这个getApiKey()持续循环调用的问题~
为什么会出现循环?
你在MyTestWidget的build方法里直接调用了Provider.of<TenderApiData>(context).getApiKey(),而build方法本身会在以下场景被触发:
- 组件首次渲染
- 依赖的
ChangeNotifier调用notifyListeners()(也就是你获取到API密钥后触发的通知)
当getApiKey()成功获取到密钥并调用notifyListeners()时,会触发MyTestWidget重新执行build方法,而build里又会再次调用getApiKey(),如此循环往复,就形成了无限调用的死循环。
另外还有个小问题:build方法应该是纯无副作用的函数,不应该在里面发起网络请求这类有副作用的操作,因为Flutter可能会频繁触发build(比如屏幕旋转、父组件更新等),这会导致不必要的重复请求。
解决办法
这里给你几个可行的方案,任选其一都能解决问题:
方案1:将组件改为StatefulWidget,在initState里发起请求
initState是StatefulWidget的生命周期方法,只会在组件初始化时执行一次,刚好适合这种只需要执行一次的网络请求:
class MyTestWidget extends StatefulWidget { @override _MyTestWidgetState createState() => _MyTestWidgetState(); } class _MyTestWidgetState extends State<MyTestWidget> { @override void initState() { super.initState(); // 这里要加listen: false,因为initState里不需要监听状态变化 Provider.of<TenderApiData>(context, listen: false).getApiKey(); } @override Widget build(BuildContext context) { var result = Provider.of<TenderApiData>(context).access_token; return Row( children: <Widget>[ Flexible(child: Text("Data: $result")) ], ); } }
方案2:在TenderApiData的构造函数里初始化请求
把数据获取逻辑放在状态类的构造函数里,这样当TenderApiData被实例化时就自动发起请求,不需要在组件里调用:
class TenderApiData with ChangeNotifier { String access_token; String url = "https://"; // 构造函数里调用getApiKey TenderApiData() { getApiKey(); } getApiKey() async { var response = await http.post(url, headers: {"Accept": "application/json"}); if (response.statusCode == 200) { access_token = json.decode(response.body)['access_token']; notifyListeners(); } } } // 然后MyTestWidget的build里只需要获取数据即可,不用调用getApiKey() class MyTestWidget extends StatelessWidget { @override Widget build(BuildContext context) { var result = Provider.of<TenderApiData>(context).access_token; return Row( children: <Widget>[ Flexible(child: Text("Data: $result")) ], ); } }
方案3:使用Consumer优化组件监听
如果你想保持组件为StatelessWidget,也可以用Consumer来监听状态变化,同时把请求逻辑放在合适的地方(比如父组件的初始化里),这样能避免不必要的build触发:
class MyTestWidget extends StatelessWidget { @override Widget build(BuildContext context) { return Consumer<TenderApiData>( builder: (context, apiData, child) { return Row( children: <Widget>[ Flexible(child: Text("Data: ${apiData.access_token}")) ], ); }, ); } } // 然后在HomePage或者MyContainer的初始化里调用请求(如果用Stateful的话) // 或者用方案2的构造函数方式
内容的提问来源于stack exchange,提问作者Dmitry Bubnenkov
相关产品推荐
相关产品推荐

