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

时间:2020-04-25 11:03:54

标签: android flutter dart


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

代码如下。我想在显示加载页面时从数据库加载一些数据。加载数据后,我使用加载的数据初始化提供程序,然后进入另一个页面。 这段代码不需要放在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 

我不知道为什么两次打印“获取数据...”,而且我不知道如何解决该问题。


我认为该问题已通过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<DataProvider>(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 

能帮我吗?

2 个答案:

答案 0 :(得分:6)

首先,请不要像前面提到的那样在build内部调用异步方法。生成方法会不断重建,这将导致您的提取方法像无限循环一样被重复。解决此问题后,由于这一部分,您仍然会收到相同的错误:

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

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

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

import 'package:flutter/scheduler.dart';

用SchedulerBinding包装导航器,以等待完成状态,然后导航到另一个屏幕。然后,在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());
  }
}

提示:您不需要在有状态小部件中传递上下文,因为您可以从任何地方访问它。

答案 1 :(得分:2)

除了构建UI外,不应将build方法用于其他任何用途。即使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);
  }
}