flutter 弹窗之系列三

Overlay 部分源码

class Overlay extends StatefulWidget{
    ...
    static OverlayState of(
        BuildContext context, {
        bool rootOverlay = false,
        Widget debugRequiredFor,
      })
    ...
}
// rootOverlay: 
// 值为false, 就近查找,找到树中最近的节点; 
// 如果为true, 则去找最顶层的节点。
class OverlayState extends State<Overlay> with TickerProviderStateMixin {
    /// 存储所有的OverlayEntry
    final List<OverlayEntry> _entries = <OverlayEntry>[];

    /// 计算OverlayEntry的插入位置
    int _insertionIndex(OverlayEntry below, OverlayEntry above) {
        if (below != null) return _entries.indexOf(below);
        if (above != null) return _entries.indexOf(above) + 1;
        return _entries.length;
    }

    /// 添加一个OverlayEntry, 在`_insertionIndex(below, above)`
    /// OverlayEntry里可以放个[Positioned]来确定位置。
    void insert(OverlayEntry entry, { OverlayEntry below, OverlayEntry above })

    /// 同insert,添加多个
    void insertAll(Iterable<OverlayEntry> entries, { OverlayEntry below, OverlayEntry above })

    /// 更新当前的Overlayentry。将newEntries更新旧有的部分
    /// 将旧有未更新的部分,添加到`_insertionIndex(below, above)`
    void rearrange(Iterable<OverlayEntry> newEntries, { OverlayEntry below, OverlayEntry above }){
        final old = LinkedHashSet<OverlayEntry>.from(_entries);
    
        setState(() {
          _entries..clear()..addAll(newEntriesList);
          old.removeAll(newEntriesList);
          _entries.insertAll(_insertionIndex(below, above), old);
        });
    }

}
class _Theatre extends MultiChildRenderObjectWidget

自定义弹出框 OverlayEntry

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {


  // 自定义Toast
   void _showOverlay({
    required String message,
  }) {
    // 创建一个OverlayEntry对象
    OverlayEntry overlayEntry = OverlayEntry(builder: (context) {
      // 外层使用Position进行定位,控制在Overlay中的位置
      return Positioned(
        top: MediaQuery.of(context).size.height * 0.7,
        child: Material(
          child: Container(
            width: MediaQuery.of(context).size.width,
            alignment: Alignment.center,
            child: Center(
              child: Card(
                color: Colors.grey,
                child: Padding(
                  padding: EdgeInsets.all(8),
                  child: Text(message),
                ),
              ),
            ),
          ),
        ),
      );
    });
    // 往Overlay中插入OverlayEntry
    Overlay.of(context).insert(overlayEntry);
    // 两秒后,移除Toast
    Future.delayed(const Duration(seconds: 2)).then((value) => overlayEntry.remove());
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: GestureDetector(
          onTap: () {
            _showOverlay(message: "消息");
          },
          child: const Text(
            '\n显示Overlay\n',
          ),
        ),
      ),
    );
  }
}

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

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

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

abstract class StateWidget<T extends StatefulWidget> extends State<T> {
  @protected
  void setState(VoidCallback fn) {
    super.setState(() {
      if (mounted) {
        fn();
      } else {
        Log.error('mounted=$mounted fn=$fn');
      }
    });
  }
}

/// demo
class TestWidget extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => StateTestWidget();
}

class StateTestWidget extends StateWidget<TestWidget> {
  @override
  void initState() {
    super.initState();
  }
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    throw UnimplementedError();
  }
}

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

安全写法

 自定义Dialog

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

class Dialog {
  BuildContext? _context;

  // 显示弹窗
  Future<void> show({
    required BuildContext context,
    required WidgetBuilder builder,
    bool barrierDismissible = true,
    Color? barrierColor = Colors.black54,
    String? barrierLabel,
    bool useSafeArea = true,
    bool useRootNavigator = true,
    RouteSettings? routeSettings,
    Offset? anchorPoint,
  }) async {
    remove();
    await showDialog(
      context: context,
      barrierDismissible: barrierDismissible,
      barrierColor: barrierColor,
      barrierLabel: barrierLabel,
      useSafeArea: useSafeArea,
      useRootNavigator: useRootNavigator,
      routeSettings: routeSettings,
      anchorPoint: anchorPoint,
      builder: (BuildContext context) {
        _context = context;
        return builder(context);
      },
    );
  }

  // 销毁弹窗
  void remove() {
    BuildContext? context = _context;
    if (context != null && context.mounted == true) {
      Navigator.pop(context);
      _context = null;
    }
  }
}

 非!判断

建议少用非!写法,对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()等接口会出现异常。

  • 18
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

️ 邪神

你自己看着办,你喜欢打赏我就赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值