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

Posted 一叶飘舟

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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("李四");
 

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

以上是关于Flutter 中 ValueNotifier<List<T>> 监听问题解决的主要内容,如果未能解决你的问题,请参考以下文章

Flutter ValueNotifier实际开发使用

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

如何在 Flutter 中添加多个相同类型的 ChangeNotifierProvider

Flutter 中 ChangeNotifier 的构建器小部件

Flutter 实现局部刷新 StreamBuilder 实例详解

Flutter 实现局部刷新 StreamBuilder 实例详解