当前位置:   article > 正文

flutter 弹窗之系列三

flutter 弹窗之系列三

Overlay 部分源码

  1. class Overlay extends StatefulWidget{
  2. ...
  3. static OverlayState of(
  4. BuildContext context, {
  5. bool rootOverlay = false,
  6. Widget debugRequiredFor,
  7. })
  8. ...
  9. }
  10. // rootOverlay:
  11. // 值为false, 就近查找,找到树中最近的节点;
  12. // 如果为true, 则去找最顶层的节点。
  1. class OverlayState extends State<Overlay> with TickerProviderStateMixin {
  2. /// 存储所有的OverlayEntry
  3. final List<OverlayEntry> _entries = <OverlayEntry>[];
  4. /// 计算OverlayEntry的插入位置
  5. int _insertionIndex(OverlayEntry below, OverlayEntry above) {
  6. if (below != null) return _entries.indexOf(below);
  7. if (above != null) return _entries.indexOf(above) + 1;
  8. return _entries.length;
  9. }
  10. /// 添加一个OverlayEntry, 在`_insertionIndex(below, above)`
  11. /// OverlayEntry里可以放个[Positioned]来确定位置。
  12. void insert(OverlayEntry entry, { OverlayEntry below, OverlayEntry above })
  13. /// 同insert,添加多个
  14. void insertAll(Iterable<OverlayEntry> entries, { OverlayEntry below, OverlayEntry above })
  15. /// 更新当前的Overlayentry。将newEntries更新旧有的部分
  16. /// 将旧有未更新的部分,添加到`_insertionIndex(below, above)`
  17. void rearrange(Iterable<OverlayEntry> newEntries, { OverlayEntry below, OverlayEntry above }){
  18. final old = LinkedHashSet<OverlayEntry>.from(_entries);
  19. setState(() {
  20. _entries..clear()..addAll(newEntriesList);
  21. old.removeAll(newEntriesList);
  22. _entries.insertAll(_insertionIndex(below, above), old);
  23. });
  24. }
  25. }
  1. class _Theatre extends MultiChildRenderObjectWidget

自定义弹出框 OverlayEntry

  1. class MyHomePage extends StatefulWidget {
  2. const MyHomePage({super.key, required this.title});
  3. final String title;
  4. @override
  5. State<MyHomePage> createState() => _MyHomePageState();
  6. }
  7. class _MyHomePageState extends State<MyHomePage> {
  8. // 自定义Toast
  9. void _showOverlay({
  10. required String message,
  11. }) {
  12. // 创建一个OverlayEntry对象
  13. OverlayEntry overlayEntry = OverlayEntry(builder: (context) {
  14. // 外层使用Position进行定位,控制在Overlay中的位置
  15. return Positioned(
  16. top: MediaQuery.of(context).size.height * 0.7,
  17. child: Material(
  18. child: Container(
  19. width: MediaQuery.of(context).size.width,
  20. alignment: Alignment.center,
  21. child: Center(
  22. child: Card(
  23. color: Colors.grey,
  24. child: Padding(
  25. padding: EdgeInsets.all(8),
  26. child: Text(message),
  27. ),
  28. ),
  29. ),
  30. ),
  31. ),
  32. );
  33. });
  34. // 往Overlay中插入OverlayEntry
  35. Overlay.of(context).insert(overlayEntry);
  36. // 两秒后,移除Toast
  37. Future.delayed(const Duration(seconds: 2)).then((value) => overlayEntry.remove());
  38. }
  39. @override
  40. Widget build(BuildContext context) {
  41. return Scaffold(
  42. appBar: AppBar(
  43. backgroundColor: Theme.of(context).colorScheme.inversePrimary,
  44. title: Text(widget.title),
  45. ),
  46. body: Center(
  47. child: GestureDetector(
  48. onTap: () {
  49. _showOverlay(message: "消息");
  50. },
  51. child: const Text(
  52. '\n显示Overlay\n',
  53. ),
  54. ),
  55. ),
  56. );
  57. }
  58. }

避免常见sentry异常的编码技巧

Dart 空指针异常 这类错误是在flutter非常常见的异常,如果不加以关注,那么sentry会收集到非常的错误数据。

  1. _CastError
  2. Null check operator used on a null value
  1. "Null check operator used on a null value" 是一个 Dart 语言中的错误提示,表示在一个空值上使用了非空检查操作符(!)。
  2. 这个错误通常发生在你使用了非空检查操作符(!)来访问一个可能为空的变量或表达式时。当你使用非空检查操作符(!)时,
  3. 你告诉编译器“我知道这个值可能为空,但是我确定它不会为空,请继续执行”。
  4. 但是,如果实际上这个值为空,那么就会抛出一个异常,即 "Null check operator used on a null value"

 setState调用异常

原因分析

组件unmount之后,还存在setState调用,由于_element = null,此时调用_element!.markNeesBuild()就会抛出异常.

一般是异步函数回调里调用setState,导致此问题出现。因此,异步函数后,调用setState需要特别关注。

目前Flutter异步函数的场景如下:

  1. Future、async/await
  2. Timer
  3. Stream
  4. Native Channel

解决方案

出现异常的地方使用 if (mounted) setState(...);

 注意:mounted == context.mounted,但使用context前,必须保证context不为null

 

 

 重写State类

由于setState异常一般不影响应用的时候,但为了解决每次setState都需要做mounted判断的处理,比较繁琐,可以封装一个父类,对setState异常做捕获,并用日志记录。

  1. abstract class StateWidget<T extends StatefulWidget> extends State<T> {
  2. @protected
  3. void setState(VoidCallback fn) {
  4. super.setState(() {
  5. if (mounted) {
  6. fn();
  7. } else {
  8. Log.error('mounted=$mounted fn=$fn');
  9. }
  10. });
  11. }
  12. }
  13. /// demo
  14. class TestWidget extends StatefulWidget {
  15. @override
  16. State<StatefulWidget> createState() => StateTestWidget();
  17. }
  18. class StateTestWidget extends StateWidget<TestWidget> {
  19. @override
  20. void initState() {
  21. super.initState();
  22. }
  23. @override
  24. Widget build(BuildContext context) {
  25. // TODO: implement build
  26. throw UnimplementedError();
  27. }
  28. }

弹窗showDialog组件回调函数引发父组件异常

安全写法

 自定义Dialog

可以自定义Dialog,在每次widget dispose的时候,把widget内场景的Dialog全部销毁,避免父组件已销毁,Dialog未销毁,触发的回调函数引发异常。代码如下所示:

  1. class Dialog {
  2. BuildContext? _context;
  3. // 显示弹窗
  4. Future<void> show({
  5. required BuildContext context,
  6. required WidgetBuilder builder,
  7. bool barrierDismissible = true,
  8. Color? barrierColor = Colors.black54,
  9. String? barrierLabel,
  10. bool useSafeArea = true,
  11. bool useRootNavigator = true,
  12. RouteSettings? routeSettings,
  13. Offset? anchorPoint,
  14. }) async {
  15. remove();
  16. await showDialog(
  17. context: context,
  18. barrierDismissible: barrierDismissible,
  19. barrierColor: barrierColor,
  20. barrierLabel: barrierLabel,
  21. useSafeArea: useSafeArea,
  22. useRootNavigator: useRootNavigator,
  23. routeSettings: routeSettings,
  24. anchorPoint: anchorPoint,
  25. builder: (BuildContext context) {
  26. _context = context;
  27. return builder(context);
  28. },
  29. );
  30. }
  31. // 销毁弹窗
  32. void remove() {
  33. BuildContext? context = _context;
  34. if (context != null && context.mounted == true) {
  35. Navigator.pop(context);
  36. _context = null;
  37. }
  38. }
  39. }

 非!判断

建议少用非!写法,对null状态做判断,如下图所示:

 说明:对于当前非常确定性非空的场景,也可能随着版本迭代的改动,导致非空。

函数参数声明

Dart允许在申明函数的时候,不申明函数参数,如下所示:

 建议:所有的函数申明,都申明函数参数,如下图所示:

 List数组index溢出

RangeError (index): Invalid value: Valid value range is empty: 1

 建议:通过index取List数组值的时候,都做index是否溢出判断,如下:

 封装扩展isRange函数

 Stream close()后,继续add()调用,出现异常

Bad state: Cannot add new events after calling close

解决方案:可以使用isClosed属性判断Stream是否已经关闭

其实Stream在flutter开发过程中,使用不多,但这里提出来是因为很多三方库内部会使用到Stream进行数据通信。

因此我们调用三方库的接口,需要保障调用顺序,例如:audioplayer dispose()之后,继续调用release()等接口会出现异常。

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号