Flutter:查找已停用小部件的祖先是不安全的

Posted

技术标签:

【中文标题】Flutter:查找已停用小部件的祖先是不安全的【英文标题】:Flutter: Looking up a deactivated widget's ancestor is unsafe 【发布时间】:2020-08-08 23:40:51 【问题描述】:

我知道已经有两个关于这个问题的帖子,但我无法解决我的问题。可能是因为在我的情况下问题不同。

代码如下。我想在显示加载页面时从数据库加载一些数据。加载数据后,我使用加载的数据初始化提供程序,然后进入另一个页面。 这段代码不需要放在StatefulWidget中,但我尝试将它放在StatefulWidget中解决问题,但没有成功。

class _InitDBDataState extends State<_InitDBData> 
  @override
  Widget build(BuildContext context) 
    _fetchData(context);
    return const Center(child: const CircularProgressIndicator());
  

  Future<void> _fetchData(BuildContext context) async 
    print('fetching data...');
    print('context: $context');
    final initData = await DBService.service.getInitialData();
    print('Data fetched');
    print('context: $context');
    Provider.of<DataProvider>(context, listen: false).init(initData);
    Navigator.of(context).pushReplacementNamed(MainScreen.routeName);
  

如果应用程序从头开始运行,我没有任何错误,但是当我执行“热重载”时,我经常收到以下错误,这很烦人,因为我需要为每个小的更改重新启动应用程序代码。

I/flutter ( 9596): fetching data...
I/flutter ( 9596): context: _InitDBData(dirty, state: _InitDBDataState#46860)
I/flutter ( 9596): fetching data...
I/flutter ( 9596): context: _InitDBData(dirty, state: _InitDBDataState#55124)
I/flutter ( 9596): Data fetched
I/flutter ( 9596): context: _InitDBData
E/flutter ( 9596): [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: Looking up a deactivated widget's ancestor is unsafe.
E/flutter ( 9596): At this point the state of the widget's element tree is no longer stable.
E/flutter ( 9596): To safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by calling dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method.
E/flutter ( 9596): #0      Element._debugCheckStateIsActiveForAncestorLookup.<anonymous closure> 
package:flutter/…/widgets/framework.dart:3508
E/flutter ( 9596): #1      Element._debugCheckStateIsActiveForAncestorLookup 
package:flutter/…/widgets/framework.dart:3522
E/flutter ( 9596): #2      Element.getElementForInheritedWidgetOfExactType 
package:flutter/…/widgets/framework.dart:3588
E/flutter ( 9596): #3      Provider.of 
package:provider/src/provider.dart:221
E/flutter ( 9596): #4      _InitDBDataState._fetchData 
package:productive_diary/initScreen.dart:46
E/flutter ( 9596): <asynchronous suspension>
E/flutter ( 9596): #5      _InitDBDataState.build 

我不知道为什么会打印两次“fetching data...”,我也不知道如何解决这个问题。


我认为问题已通过Saman Salehi 的解决方案解决,但在调试模式下工作时,我在 _fetchData 函数中遇到了相同的异常,现在在函数 initState()

Exception has occurred.
FlutterError (Looking up a deactivated widget's ancestor is unsafe.
At this point the state of the widget's element tree is no longer stable.
To safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by calling dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method.)

应用Stewie Griffin 建议的编辑后,我又遇到了一个错误。

错误在线Provider.of&lt;DataProvider&gt;(context, listen: false).init(initData);

我在热重载期间得到它。它似乎没有其他错误那么常见,所以 Stewie Griffin 的回答肯定提高了我的Stewie Griffin 的稳定性

E/flutter (23815): [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: NoSuchMethodError: The getter 'owner' was called on null.
E/flutter (23815): Receiver: null
E/flutter (23815): Tried calling: owner
E/flutter (23815): #0      Object.noSuchMethod  (dart:core-patch/object_patch.dart:53:5)
E/flutter (23815): #1      Provider.of 
package:provider/src/provider.dart:193
E/flutter (23815): #2      _InitDBDataState._fetchData 
package:productive_diary/initScreen.dart:49
E/flutter (23815): <asynchronous suspension>
E/flutter (23815): #3      _InitDBDataState.initState 

你能帮帮我吗?

【问题讨论】:

这个错误和你使用的包有关。尝试将您的函数放在didChangeDependencies() 而不是initState()。它可能会解决问题。如果没有,请参阅此线程:github.com/rrousselGit/provider/issues/183 【参考方案1】:

您不应该将 build 方法用于构建 UI 之外的任何事情。 build 可以随时调用,即使它不在屏幕上。

我会将_fetchData 移动到initState,这样就不会在build 方法中引起任何冲突。

class _InitDBDataState extends State<_InitDBData> 

  @override
  void initState() 
    super.initState();
    _fetchData(context);
  

  @override
  Widget build(BuildContext context) 
    return const Center(child: const CircularProgressIndicator());
  

  Future<void> _fetchData(BuildContext context) async 
    print('fetching data...');
    print('context: $context');
    final initData = await DBService.service.getInitialData();
    print('Data fetched');
    print('context: $context');
    Provider.of<DataProvider>(context, listen: false).init(initData);
    Navigator.of(context).pushReplacementNamed(MainScreen.routeName);
  

【讨论】:

问题:如果您在 initState 中等待 Future,这不会导致糟糕的性能吗?在这种情况下,您不应该使用 StreamProvider 或 FutureProvider 吗?【参考方案2】:

首先,永远不要像上面提到的那样在 build 内部调用异步方法。 Build 方法会不断重建,它会导致您的 fetch 方法像无限循环一样重复。修复后,由于这部分,您仍然会遇到相同的错误:

Navigator.of(context).pushReplacementNamed(MainScreen.routeName);

您不应在构建期间调用 Navigator。以下是您需要做的:

将此行添加到文件顶部以使用SchedulerBinding

import 'package:flutter/scheduler.dart';

使用 SchedulerBinding 包装 Navigator 以等待完成状态,然后再导航到另一个屏幕。然后,在initState 中调用你的异步方法。

class _InitDBDataState extends State<_InitDBData> 
@override
  void initState() 
    // Call your async method here
    _fetchData();
    super.initState();
  

  Future<void> _fetchData() async 
    print('fetching data...');
    print('context: $context');
    final initData = await DBService.service.getInitialData();
    print('Data fetched');
    print('context: $context');
    Provider.of<DataProvider>(context, listen: false).init(initData);

    // Wrap Navigator with SchedulerBinding to wait for rendering state before navigating
    SchedulerBinding.instance.addPostFrameCallback((_) 
      Navigator.of(context).pushReplacementNamed(MainScreen.routeName);
    );
  
  @override
  Widget build(BuildContext context) 
    return Center(child: CircularProgressIndicator());
  

提示:您不需要在 Stateful Widget 中传递上下文,因为您可以从任何地方访问它。

【讨论】:

最佳答案!我整天都在寻找如何解决这个问题,尝试了一切,但有了你的详细解释,我能够修复我的代码中的所有错误,因为我在 initState 中有 5 Navigator.of(context).pushReplacementNamed() 。谢谢一百万!

以上是关于Flutter:查找已停用小部件的祖先是不安全的的主要内容,如果未能解决你的问题,请参考以下文章

Flutter:错误是查找已停用小部件的祖先是不安全的

Flutter & Navigator & ImagePicker : 为啥我可以进入下一页?查找已停用小部件的祖先是不安全的

未处理的异常:查找已停用小部件的祖先是不安全的。如何解决这个问题?

在dispose()方法内部调用带有Provider.of(context)的方法会导致“查找已停用的小部件的祖先是不安全的。”

提供者:查找已停用小部件的祖先是不安全的

使用提供程序和快餐栏查找已停用小部件的祖先是不安全的