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.maybeOf
,context == 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");
}
}