Flutter 是否退出页面的异常 (异步调用场景)

2022-07-22增补

为什么崩溃?因为退出页面 RenderTree、ElementTree 就都被销毁了。

Element是在Widget Tree、Render Tree之间起到链接作用。
BuildContext 代表了真正的Element对象,但避免你直接操控Element。
BuildContext build方法里的 BuildContext 形参,包含了widget此次构建中,对应Widget Tree 在这个插入节点的相关信息。

/// [BuildContext] objects are actually [Element] objects. The [BuildContext]
/// interface is used to discourage direct manipulation of [Element] objects.

/// The given [BuildContext] contains information about the location in the
/// tree at which this widget is being built. For example, the context
/// provides the set of inherited widgets for this location in the tree. A
/// given widget might be built with multiple different [BuildContext]
/// arguments over time if the widget is moved around the tree or if the
/// widget is inserted into the tree in multiple places at once.

退出页面后,页面销毁,context对应Element,当然也就销毁了。

结论

  • 异步延迟操作,判断页面是否退出, StatefulWidget 要使用 mounted 做判断。StatelessWidget 目前不知道拿啥做判断(可以使用回调抛出去,但太麻烦了哦)
  • 页面退出时,不可以使用 Navigator.maybeOfcontext == null 或任何 context 调用,
  • 没退出页面,仍要使用 Navigator.maybeOf代替 Navigator.of因为 navigator 可能是空
    示例代码:

建议编码

// StatefulWidget 类中

// 异步回调开始执行......

if (!mounted) {   // 判断用户是否退出页面
	return;
}

// 如果用到了Navigator,需要做 null 判断
var navigator = Navigator.maybeOf(context);
if (navigator == null) {
    return;
}

// StatelessWidget

  • 建议不要发起异步,因为已知 无法直接判断 用户是否退出。
  • 当然可以自己写个回调,让外部判断。 与其如此,不如用StatefulWidget
  • 不要调用 Navigator.maybeOf,用户退出页面,会异常

场景

  • 用户在网络回调执行前 退出页面。回调内调用 Navigator,show dialog, context == null , 或调用context

原因

  • context在 StatefulWidget 内部是调用的getter 方法,不是属性调用。 改方法 最后做了 !非空校验。

release 版本会在 _element!; 处异常

  BuildContext get context {
    assert(() {
      if (_element == null) {
        throw FlutterError(
          'This widget has been unmounted, so the State no longer has a context (and should be considered defunct). \n'
          'Consider canceling any active work during "dispose" or using the "mounted" getter to determine if the State is still active.',
        );
      }
      return true;
    }());
    return _element!;
  }

mounted做的判断

bool get mounted => _element != null;

Navigator.of 内部判断

    assert(() {
      if (navigator == null) {
        throw FlutterError(
          'Navigator operation requested with a context that does not include a Navigator.\n'
          'The context used to push or pop routes from the Navigator must be that of a '
          'widget that is a descendant of a Navigator widget.',
        );
      }
      return true;
    }());
    return navigator!;
  }

报错方法栈

StatefulWidget 调用 context 报错

  • 退出页面时
I/flutter (31806): Future click
I/flutter (31806): Future running context: noNull
E/flutter (31806): [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: Null check operator used on a null value
E/flutter (31806): #0      StatefulElement.state (package:flutter/src/widgets/framework.dart:4712)
E/flutter (31806): #1      Element.findAncestorStateOfType (package:flutter/src/widgets/framework.dart:4054)
E/flutter (31806): #2      Navigator.maybeOf (package:flutter/src/widgets/navigator.dart:2598)
E/flutter (31806): #3      NetPage.futureCall.<anonymous closure> (package:flutter_app/net/view/NetPage.dart:60)
E/flutter (31806): #4      new Future.delayed.<anonymous closure> (dart:async/future.dart:393)
E/flutter (31806): #5      _rootRun (dart:async/zone.dart:1420)
E/flutter (31806): #6      _CustomZone.run (dart:async/zone.dart:1328)
E/flutter (31806): #7      _CustomZone.runGuarded (dart:async/zone.dart:1236)
E/flutter (31806): #8      _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:1276)
E/flutter (31806): #9      _rootRun (dart:async/zone.dart:1428)
E/flutter (31806): #10     _CustomZone.run (dart:async/zone.dart:1328)

StatelessWidget 调用 Navigator.maybeOf 报错

  • 退出页面时
I/flutter (31806): Future click
I/flutter (31806): Future running context: noNull
E/flutter (31806): [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: Null check operator used on a null value
E/flutter (31806): #0      StatefulElement.state (package:flutter/src/widgets/framework.dart:4712)
E/flutter (31806): #1      Element.findAncestorStateOfType (package:flutter/src/widgets/framework.dart:4054)
E/flutter (31806): #2      Navigator.maybeOf (package:flutter/src/widgets/navigator.dart:2598)
E/flutter (31806): #3      NetPage.futureCall.<anonymous closure> (package:flutter_app/net/view/NetPage.dart:60)
E/flutter (31806): #4      new Future.delayed.<anonymous closure> (dart:async/future.dart:393)
E/flutter (31806): #5      _rootRun (dart:async/zone.dart:1420)
E/flutter (31806): #6      _CustomZone.run (dart:async/zone.dart:1328)
E/flutter (31806): #7      _CustomZone.runGuarded (dart:async/zone.dart:1236)
E/flutter (31806): #8      _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:1276)
E/flutter (31806): #9      _rootRun (dart:async/zone.dart:1428)
E/flutter (31806): #10     _CustomZone.run (dart:async/zone.dart:1328)

Navigator.of 报错

  • 线上崩溃收集,不清楚用户是否 退出页面
Null check operator used on a null value
#0      Navigator.of (package:flutter/src/widgets/navigator.dart:2719)

实验代码

你得自己改改,和上述不完全对应。

StateFulWidget 实验代码

import 'dart:async';
import 'dart:convert' as convert;
import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';

class NetPage extends StatefulWidget {
  NetPage({Key? key}) : super(key: key);

  @override
  _NetPageState createState() => _NetPageState();
}

class _NetPageState extends State<NetPage> {
  @override
  void initState() {
    super.initState();
  }

  @override
  void dispose() {
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('NetPage'),
      ),
      body: Container(
          child: Row(
            children: [
              ElevatedButton(
                  onPressed: () {
                    futureCall();
                  },
                  child: Text("Click Future")),
              ElevatedButton(
                  onPressed: () {
                    timerCall();
                  },
                  child: Text("Click Timer")),
              ElevatedButton(
                  onPressed: () {
                    tryException();
                  },
                  child: Text("Null Exception")),
            ],
          )),
    );
  }

  Future requestNet() async {
    var url = Uri.https('www.baidu.com', '');

    var response = await http.get(url);
    if (response.statusCode == 200) {
      print("Success response body: ");
      print('${response.body}');
    } else {
      print('Request failed with status: ${response.statusCode}.');
    }
  }

  String? test;
  get message  {
    return test!;
  }

  void futureCall() {
    print("Future click");
    Future.delayed(Duration(seconds: 4), () {
      // print("Future mount test: $mounted");
      // print("Future running context: ${context == null ? "null" : "noNull"}");
      var navigatorState = Navigator.maybeOf(context);
      print("Future NavigatorState is ${navigatorState == null ? "null": "noNull"}");
      return "hello";
    });
  }

  void timerCall() {
    print("Timer click");

    Timer(Duration(seconds: 5), () {
      print("Timer running context: ${context == null ? "null" : "noNull"}");
      var navigatorState = Navigator.maybeOf(context);
      print("Timer NavigatorState is ${navigatorState == null ? "null": "noNull"}");
    });
  }

  void tryException() {
    print("Timer click getMessage $message");
  }

}

StatelessWidget 实验代码

import 'dart:async';
import 'dart:convert' as convert;
import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';

class NetPage extends StatelessWidget {
  NetPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {


    return Scaffold(
      appBar: AppBar(
        title: Text('NetPage'),
      ),
      body: Container(
          child: Row(
            children: [
              ElevatedButton(
                  onPressed: () {
                    futureCall(context);
                  },
                  child: Text("Click Future")),
              ElevatedButton(
                  onPressed: () {
                    timerCall(context);
                  },
                  child: Text("Click Timer")),
              ElevatedButton(
                  onPressed: () {
                    tryException(context);
                  },
                  child: Text("Null Exception")),
            ],
          )),
    );
  }

  Future requestNet() async {
    var url = Uri.https('www.baidu.com', '');

    var response = await http.get(url);
    if (response.statusCode == 200) {
      print("Success response body: ");
      print('${response.body}');
    } else {
      print('Request failed with status: ${response.statusCode}.');
    }
  }

  String? test;
  get message  {
    return test!;
  }

  void futureCall(BuildContext context) {
    print("Future click");
    Future.delayed(Duration(seconds: 4), () {
      // print("Future mount test: $mounted");
      print("Future running context: ${context == null ? "null" : "noNull"}");
      var navigatorState = Navigator.maybeOf(context, rootNavigator: true);
      print("Future NavigatorState is ${navigatorState == null ? "null": "noNull"}");
      return "hello";
    });
  }

  void timerCall(BuildContext context) {
    print("Timer click");

    Timer(Duration(seconds: 5), () {
      print("Timer running context: ${context == null ? "null" : "noNull"}");
      var navigatorState = Navigator.maybeOf(context);
      print("Timer NavigatorState is ${navigatorState == null ? "null": "noNull"}");
    });
  }

  void tryException(BuildContext context) {
    print("Timer click getMessage $message");
  }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值