Flutter 中 ValueNotifier<List<T>> 监听问题解决

1. 起因

开发中遇到一个问题 ValueNotifier<List<T>> 监听失败, 初步确认原因是数组值发生改变但是地址未发生改变,与 iOS 监听数组需要特别处理一样;需要二次赋值触发地址改变,触发监听机制;

2.结果:完美解决问题

最终简单封装如下:

/// 泛型数组监听
class ValueNotifierList<T> extends ValueNotifier<List<T>> {

  ValueNotifierList(List<T> initValue) : super(initValue);

  void add(T item) {
    value.add(item);
    _copyValue();
  }

  /// 删除
  void remove(int index) {
    if (value.length < index) {
      return;
    }
    value.removeAt(index);
    _copyValue();
  }

  /// 删除最后
  void removeLast() {
    if (value.length == 0) {
      return;
    }
    value.removeLast();
    _copyValue();
  }

  void removeAll() {
    value.clear();
    _copyValue();
  }
  /// 利用深copy 重新赋值,触发监听
  void _copyValue() {
    value = [...value];
  }

  @override
  String toString() {
    return "${this.runtimeType} 共 ${value.length} 件商品}";
  }
}

3. example:

/// ValueNotifier
static ValueNotifierList valueNotifierList = ValueNotifierList(<OrderModel>[]);
/// ValueNotifier(addListener无效 因为数组地址未发生改变, 推荐使用 ValueNotifierList)
static ValueNotifier<List<OrderModel>> valueNotifierListOrigin = ValueNotifier(<OrderModel>[]);

...

@override
void initState() {
  super.initState();
  
  valueNotifierList.addListener(update);
  valueNotifierListOrigin.addListener(update);
}

@override
void dispose() {
  valueNotifierList.removeListener(update);
  valueNotifierListOrigin.removeListener(update);
  
  super.dispose();
}

...

void update() {
  showSnackBar(SnackBar(content: Text("数据变化监听回调, 刷新重建界面",)), true);
  setState(() {});
}

...

void handleActionNum({required ValueNotifierModel e, required int value, required int idx}) {
  switch (e.name) {
    case "valueNotifierList":
      {
        final e = OrderModel(name: '商品', id: 99, pirce: 1.00);
        if (value > 0) {
          valueNotifierList.add(e);
        } else {
          valueNotifierList.removeLast();
        }

        ddlog(valueNotifierList.toString());
        // ddlog("${cartModelOneKey.totalPrice}");
      }
      break;

    case "valueNotifierListOrigin":
      {
        final e = OrderModel(name: '商品', id: 99, pirce: 1.00);
        if (value > 0) {
          valueNotifierListOrigin.value.add(e);
        } else {
          valueNotifierListOrigin.value.removeLast();
        }
        
        update();///监听无效,需要手动调整

        ddlog(valueNotifierListOrigin.value.length.toString());
        // ddlog("${cartModelOneKey.totalPrice}");
      }
      break;
    default:
      break;
  }
}

延伸:Flutter ValueNotifier 异步通信、ValueListenableBuilder异步更新数据

 在 Flutter 中可用于异步通信的方案有如下:

  • Provider 
  • ValueNotifier
  • Stream
  • EventBus (不考虑使用)
  • Bloc BLoC 

本文章讲述使用 Navigator 更新页面 A 的数据、ValueListenableBuilder 的基本使用、自定义 ValueNotifier 进行局部数据的更新

1 前言
在实际项目开发中,有一种业务需求就是 页面A 进入页面B ,在页面B中数据发生改变后需要更新页面A 中的内容,其实第一种方案可以考虑使用 then函数回调,如下代码清单1-1所示,在页面A中以动态路由的方式打开页面TestBPage,并实现 Navigator 的then 函数,then 函数会在 TestBPage 页面关闭时回调。

///代码清单 1-1 
 void openPageFunction(BuildContext context) {
   ///以动态路由的方式打开
   Navigator.of(context).push(
     MaterialPageRoute(
       builder: (BuildContext context) {
         return TestBPage();
       },
     ),
     ///页面 TestBPage 关闭后会回调 then 函数
     ///其中参数 value 为回传的参数
   ).then((value) {
     if (value != null) {
       setState(() {
         _message = value;
       });
     }
   });
 }

当在页面 TestBPage 关闭时,可以主动回传参数,如下代码 清单 1-2 所示:

 ///代码 清单 1-2
 OutlineButton buildOutlineButton(BuildContext context) {
   return OutlineButton(
     child: Text("返回页面 A "),
     onPressed: () {
       String result = "345";
       Navigator.of(context).pop(result);
     },
   );
 }


这一种方法的一个实际应用场所如一个订单的详情页面,打开下一个页面进行操作后,再返回当前页面后需要刷新页面的数据,此种场景就可使用这种方法。

使用 then 函数达成的数据传递或者说页面刷新,对于用户来讲是可见的,就是有时数据刷新的慢点,用户是可以有感觉的,使用ValueNotifier可以达到无感刷新。

2 ValueNotifier 的基本使用
ValueNotifier 需要结合组件 ValueListenableBuilder 来使用。

/// 第一步 定义 ValueNotifier  这里传递的数据类型为 String
ValueNotifier<String> _testValueNotifier = ValueNotifier<String>('');
///第二步定义 数据变化后监听的 Widget
Widget buildValueListenableBuilder() {
  return ValueListenableBuilder(
    ///数据发生变化时回调
    builder: (context, value, child) {
      return Text(value);
    },
    ///监听的数据
    valueListenable: _testValueNotifier,
    child: Text(
      '未登录',
      style: TextStyle(color: Colors.red),
    ),
  );
}
///第三步就是数据变化后
 void testFunction() {
   ///赋值更新
   _testValueNotifier.value = '传递的测试数据';
 }

在上述代码片段中,传递更新的数据是一个String,当然在业务开发场景中,会有更多自定义的 Model,直接替换上述的String就可以。

3 自定义 ValueNotifier 局部更新
如有一个用户数据类型的Model定义如下 :

///实际中变量可能足够的多
class UserInfo {
  String name;
  int age ;
}


我们期望只修改其中的如 age 一个属性的值,此时就需要自定 ValueNotifier ,如下代码清单 1-3 所示:

///代码清单 1-3 
///自定义 ValueNotifier
/// UserInfo 为数据类型
class UserNotifier extends ValueNotifier<UserInfo> {

  UserNotifier(UserInfo userInfo): super(userInfo);

  void setName(String name) {
    ///赋值 这里需要注意的是 如果没有给 ValueNotifier 赋值 UserInfo 对象时
    /// value 会出现空指针异常
    value.name =name;
    ///通知更新
    notifyListeners();
  }

}


然后在使用的时候,创建 UserNotifier 如下 :

///第一步
UserNotifier _testUserNotifier = UserNotifier(UserInfo(name: "", age: 0));

构建 ValueListenableBuilder

///第二步 定义
 Widget buildUserListenableBuilder() {
   return ValueListenableBuilder(
     ///数据发生变化时回调
     builder: (context, value, child) {
       return Text("姓名是:${value.name}  年龄是: ${value.age}");
     },
     ///监听的数据
     valueListenable: _testUserNotifier,
   );
 }


当数据变化时进行更新操作

 void testUserFunction() {
   _testUserNotifier.setName("李四");
 }

这种应用场景如实际项目开发中的修改用户数据,只修改了其中的一个属性数据,可以考虑使用这种方法,当然有很多情况大家是不考虑这种细节的,直接全部更新的,但是所有的细节综合起来,就解决了 你的应用为什么体验总是差点的问题。
 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值