在 InitState 未处理异常中显示对话框:查找已停用小部件的祖先是不安全的

Posted

技术标签:

【中文标题】在 InitState 未处理异常中显示对话框:查找已停用小部件的祖先是不安全的【英文标题】:Show Dialog In InitState Unhandled Exception: Looking up a deactivated widget's ancestor is unsafe 【发布时间】:2020-09-18 06:07:41 【问题描述】:

第一次出现屏幕时,我想检查是否启用了用户 GPS 并授予了位置权限。然后,如果其中一个不满足,我会显示对话框以打开应用设置。

源代码

_initPermission(BuildContext context) async 
    final geolocationStatus = await commonF.getGeolocationPermission();
    final gpsStatus = await commonF.getGPSService();
    if (geolocationStatus != GeolocationStatus.granted) 
      showDialog(
        context: context,
        builder: (ctx) 
          return commonF.showPermissionLocation(ctx);
        ,
      );
     else if (!gpsStatus) 
      showDialog(
        context: context,
        builder: (ctx) 
          return commonF.showPermissionGPS(ctx);
        ,
      );
    
  

然后我在 initState 中这样调用这个函数:

初始化状态

void initState() 
    super.initState();
    Future.delayed(Duration(milliseconds: 50)).then((_) => _initPermission(context));
  

问题是,每次屏幕出现时都会给我这样的错误:

错误

[ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: 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.

我做了什么:

像这样改变 InitState
//1
 WidgetsBinding.instance.addPostFrameCallback((_) 
      _initPermission(context);
 );
//2
  SchedulerBinding.instance.addPostFrameCallback((_) => _initPermission(context));
//3
Timer.run(()  
      _initPermission(context); 
  )
为脚手架添加全局键
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
 Scaffold(
        key: _scaffoldKey,
搜索与我类似的问题
    Show Dialog In Initstate Looking up a deactivated widget's ancestor is unsafe Load Data In Initstate

我做的结果什么都没有,第一次出现的时候还是出现错误。

但奇怪的是,这只发生在第一次出现屏幕时。当我执行 热重启 时,错误消息消失了。

[第一次出现屏幕失败]

[热重启,错误消失]

【问题讨论】:

【参考方案1】:

我没有办法测试它(我没有 GPS 包)但尝试改变

Future.delayed(Duration(milliseconds: 50)).then((_) => _initPermission(context));

Future.delayed(Duration(milliseconds: 50)).then((_) => _initPermission()); //this inside the initstate

_initPermission() async
final geolocationStatus = await commonF.getGeolocationPermission();
final gpsStatus = await commonF.getGPSService();
if (geolocationStatus != GeolocationStatus.granted) 
  await showDialog(
    context: context,
    builder: (ctx) => commonF.showPermissionLocation
    ,
  );
 else if (!gpsStatus) 
  await showDialog(
    context: context,
    builder: (ctx) => commonF.showPermissionGPS
    ,
  );

 // the stateful widget can use the context in all its methods without passing it as a parameter

错误在热启动中消失,因为它只是刷新小部件的状态,但它已经创建(如果您进行热启动并在 initState 中打印一些内容,例如 print('This is init');,你不会看到它要么是因为刷新没有处理和初始化小部件,所以它不会再次运行那段代码)

编辑

根据您的要点,我只是做了一个可重现的最小示例,并在 DartPad 中运行没有问题,稍后我将在 VS 上尝试,但现在您可以检查是否有任何不同

enum GeolocationStatusgranted, denied //this is just for example

class MyWidget extends StatefulWidget 
  @override
  _MyWidgetState createState() => _MyWidgetState();


class _MyWidgetState extends State<MyWidget> 


showPermissionGPS() 
    return AlertDialog(
        title: Text('GPSStatus'),
      );
  

  _showPermissionLocation() 
    return AlertDialog(
        title: Text('Permission Localization'),
      );
  

  @override
  initState()
    super.initState();
    Future.delayed(Duration(milliseconds: 50)).then((_) => _initPermission());
  


_initPermission() async
  final geolocationStatus = await Future<GeolocationStatus>.value(GeolocationStatus.granted); //mocked future with a response to test
  final gpsStatus = await Future<bool>.value(false); //mocked future with a response to test
  if (geolocationStatus != GeolocationStatus.granted) 
   await showDialog(
      context: context,
      builder: (ctx) => _showPermissionLocation() //this mock commonF.showPermissionLocation and returns an AlertDialog
   );
   else if (!gpsStatus) 
   await showDialog(
    context: context,
    builder: (ctx) => _showPermissionGPS() //this mock commonF.showPermissionGPS and returns an AlertDialog
  );
 


  @override
  Widget build(BuildContext context) 
    return Text('My text');
  

【讨论】:

已经尝试这个,它会产生新的错误'context != null': is not true.。错误将指定给initStateshowDialog commonF 来自哪个包? commonF 是存储在 1 个文件中的所有分离函数。 gist.github.com/zgramming/2f431fdf1db72c7b9b6774017909a0eb commonF.showPermissionGPS 和 commonF.showPermissionLocation 怎么样?是那些返回小部件的方法吗?我编辑了对话框的答案以返回该 ctx gist.github.com/zgramming/2f431fdf1db72c7b9b6774017909a0eb 我更新了要点,使其更加清晰。 showPermissionGPS & showPermissionLocation 它返回 alertDialog。可以在这里看到gist.github.com/zgramming/95fc8e55da2b9b85cb0fd57a24b840c6

以上是关于在 InitState 未处理异常中显示对话框:查找已停用小部件的祖先是不安全的的主要内容,如果未能解决你的问题,请参考以下文章

防止 wxPython 显示“未处理的异常”对话框

异常处理

来自 MFC 对话框中使用的托管 C# 用户控件的未处理异常

未处理的警报异常:存在模式对话框(Selenium)

CustomDraw 中的 SetWindowLong 导致未处理的异常

对话框片段已添加异常未抛出