Flutter 仿iOS通知的实现

2,430 阅读2分钟

Flutter关于Notification,没什么可说的,可以实现将数据从子组件向父组件传递。

但是如果要是实现多页面、跨页面、广播进行一对多的通知,类似iOS中的NSNotificationCenter ,在Flutter中暂时没有还发现。

类似这样的功能:

通知.png

3个页面,其中page3发出通知,page1page2接收通知,做出相应操作。 有很多方法都可以实现这样的功能,这里探讨下用通知怎么实现。

因为Flutter中并没有提供类似iOS中的NSNotificationCenter 功能,那我们可不可以自己撸一个呢?

思路:

首先想到的是将通知中心设计成单例类,以方便使用;其次需要提供加入通知、发出通知、移除通知等方法。

  • 加入通知 :记录每一次加入的通知,并以name标记不同的通知;
  • 发出通知:根据name在通知中心中拿到对应的通知,并执行通知方法;
  • 移除通知:根据name在通知中心中拿到对应的通知,并移除对应通知;

以上只是初步的设想,肯定有不完善的地方,我们后面会一一完善。

思路有了,下面就开始撸代码实现了:

class RCNotificationCenter {
  /// 单例
  factory RCNotificationCenter() => _getInstance();
  static RCNotificationCenter get instance => _getInstance();
  static RCNotificationCenter _instance;
  RCNotificationCenter._internal();
  static RCNotificationCenter _getInstance() {
    if (_instance == null) {
      _instance = RCNotificationCenter._internal();
    }
    return _instance;
  }

  /// 创建通知中心,postName与通知--对应
  Map<String, Function(dynamic value)> _poolMap = Map<String, Function(dynamic value)>();

  /// 添加监听者方法(加入通知)
  void addObserver(String postName,notification(dynamic value)) {
    _poolMap[postName] = notification;
  }

  /// 发送通知
  void postNotification(String postName, dynamic value) {
    /// 判断Map是否含有postName
    if (_poolMap.containsKey(postName)) {
      /// 拿到对应的通知,并执行
      _poolMap[postName](value);
    }
  }
  /// 根据postName移除对应通知
  void removeNotification(String postName) {
    if (_poolMap.containsKey(postName)) {
      _poolMap.remove(postName);
    }
  }
  /// 清空通知中心
  void removeAll(){
    _poolMap.clear();
  }
}

使用时:

/// 加入通知
RCNotificationCenter().addObserver("postName",(value){
  print("接收到通知:$object");
});

/// 发送通知
RCNotificationCenter().postNotification("postName", value);

问题:

主要有两个:

  • 虽然可以实现通知功能,但是由于使用了Map,每次加入后,对与同一个name直接重新赋值覆盖,造成了相同的name的通知,只会执行最后一条,并不能实现通知的一对多。

  • 移除通知存在问题,在多个页面加入同一个通知后,会出现本来只想移除某一个页面的通知,结果所有的对应相同name的通知都会移除。无法实现对应单个特定页面的通知进行移除。

完善:

为解决以上问题,每个通知需要对应三个元素,分别是:namekey(具体是谁加入的,类似于iOS的self,Flutter的this,后期可以根据此元素来移除特定页面的通知,而不会影响其他页面相同的通知,做到谁加入,谁移除)、notification通知执行方法。

为此,笔者创建一个通知的模型用来对应每一个通知。

完善后的代码:

class RCNotificationCenter {
  // 单例
  factory RCNotificationCenter() => _getInstance();
  static RCNotificationCenter get instance => _getInstance();
  static RCNotificationCenter _instance;
  RCNotificationCenter._internal();
  static RCNotificationCenter _getInstance() {
    if (_instance == null) {
      _instance = RCNotificationCenter._internal();
    }
    return _instance;
  }
  //创建通知中心
  List<RCNotificationModel> pool = [];

  //添加监听者方法(加入通知中心)
  void addObserver(String postName, dynamic key,void notification(dynamic value)) {
    RCNotificationModel model = RCNotificationModel.fromList([postName,key,notification]);
    pool.add(model);
  }

  //发送通知
  void postNotification(String postName, dynamic value) {
    /// 遍历拿到对应的通知,并执行
    pool.forEach((element) {
      if(element.postName == postName){
        element.notification(value);
      }
    });
  }
  /// 根据postName移除通知
  void removeOfName(String postName) {
    /// 线程安全,不可使用forEach执行添加、移除等操作
    pool.removeWhere((element) => element.postName == postName);
  }

  /// 根据key移除通知
  void removeOfKey(dynamic key){
    pool.removeWhere((element) => element.key == key);
  }
  /// 清空通知中心
  void removeAll(){
    pool.clear();
  }
}

/// 通知模型
class RCNotificationModel{
  String postName;
  dynamic key; /// 根据key标记是谁加入的通知,一般直接传widget就好
  Function(dynamic value) notification;
  /// 简单写一个构造方法
  RCNotificationModel.fromList(List list){
    this.postName = list.first;
    this.key = list[1];
    this.notification = list.last;
  }
}

使用时:

/// 加入通知中心
RCNotificationCenter().addObserver("postName",this,(value){
  print("接收到通知:$value");
});
/// 发出通知
RCNotificationCenter().postNotification("postName", value);
/// 移除名字为postName的所有通知
RCNotificationCenter().removeOfName("postName");
/// 移除标记为key的所有通知,一般在dispose()调用
RCNotificationCenter().removeOfKey(this);

以上就是实现了类似iOS中的NSNotificationCenter通知中心,基本上可以实现多页面、跨页面、广播进行一对多的通知。 可优化部分:监听加入通知的key,key资源释放了,自动调用移除通知方法。iOS可以绑定对象以此监听对象什么时候被释放。目前Flutter还在学习中!

当然,有什么更好的方案,欢迎留言!