Flutter集合变化通知插件collection_change_notifier的使用

Flutter集合变化通知插件collection_change_notifier的使用

目的

Flutter 提供了 ChangeNotifier 用于处理当可变对象的属性发生变化时触发更新并使小部件重新构建(例如 provider 包)。然而,当处理单个对象下的多个项目时,需要自行实现特定功能,这在处理集合时可能会受到限制。因此,此插件提供了带有 ChangeNotifier 集成的 ListMapSet

它类似于 .NET 中的 BindingList 的工作方式。

安装

编辑 pubspec.yaml

从 pub.dev

dependencies:
    collection_change_notifier: ^1.0.0+1 # 或者 ''>=1.0.0 <1.1.0' 如果需要相同的小版本

从 Git(适用于不稳定版本)

dependencies:
    git:
        url: https://github.com/rk0cc/collection_change_notifier.git
        ref: (提交哈希、分支或标签名)

使用

大多数情况下,在编辑集合中的项目时已经集成了 notifyListener,除非直接更改元素状态,这需要使用 modify 来通知更新:

class IntState {
    int state;

    IntState(this.state);
}

final ListChangeNotifier<IntState> lcnis = ListChangeNotifier()..add(IntState(1));

// 将 lcnis 附加到 ChangeNotifierProvider.value

// 不要直接更改元素状态,这种操作不会触发 Flutter 重建上下文。
lcnis[0].state = 2;

// 如果想要在通过 `modify` 更改元素状态时触发 Flutter 重建,则调用 modify:
lcnis.modify(0, (item) {
    item.state = 2;
});

// 对于 Map:
final MapChangeNotifier<String, IntState> mscnis = MapChangeNotifier()..["one"] = IntState(1);

mscnis.modify("one", (item) {
    // 必须进行空检查以确保已分配(当值类型为非空时)
    if (item != null) {
        item.state = 2;
    }
    // item!.state = 2; // (不推荐,会使错误处理更加复杂)
});

限制

该插件仅基于类方法实现,没有扩展方法的覆盖方法。扩展方法倾向于多次调用 notifyListeners,从而导致构建错误抛出。

许可证

BSD-3


示例代码

import 'package:collection_change_notifier/collection_change_notifier.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(const ExampleApp());
}

/// 创建一个包含可变字符串的可变对象。
class StringNode {
  /// 上下文的状态,可以更改。
  String ctx;

  /// 使用给定的初始值创建 [StringNode]。
  StringNode(this.ctx);
}

/// 应用程序的入口点。
class ExampleApp extends StatelessWidget {
  const ExampleApp({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) => 
      // 使用 ChangeNotifierProvider 创建一个空的 ListChangeNotifier。
      ChangeNotifierProvider<ListChangeNotifier<StringNode>>(
          create: (context) => ListChangeNotifier(),
          builder: (context, child) => MaterialApp(
              title: "集合变化通知器示例",
              home: const ExampleAppHome()));
}

/// 应用程序的首页。
class ExampleAppHome extends StatelessWidget {
  const ExampleAppHome({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) => Scaffold(
        appBar: AppBar(
            title: const Text("集合变化通知器列表演示"),
            actions: <IconButton>[
              // 列出应用到 ListChangeNotifier 的 StringNode
              IconButton(
                  onPressed: () => Navigator.push(
                      context,
                      MaterialPageRoute(
                          builder: (context) => const ExampleAppList())),
                  icon: Icon(Icons.list))
            ]),
        body: Center(
            child: ListView(
                padding: const EdgeInsets.all(20),
                shrinkWrap: true,
                children: <Widget>[
              // 显示 StringNode 列表中的项目数量。
              Text(
                  "项目计数: ${Provider.of<ListChangeNotifier<StringNode>>(context).length}",
                  style: const TextStyle(fontSize: 18),
                  textAlign: TextAlign.center)
            ])),
        floatingActionButton: FloatingActionButton(
            // 创建新的 StringNode 数据
            onPressed: () => Navigator.push(context,
                MaterialPageRoute(builder: (context) => ExampleAppNodeEdit())),
            child: Icon(Icons.add)),
      );
}

/// 用于创建或编辑 [StringNode] 的小部件。
class ExampleAppNodeEdit extends StatefulWidget {
  /// 指定要修改的 [StringNode] 的索引。
  ///
  /// 如果留空,则假定为创建新的 [StringNode]。
  final int? editIdx;

  /// 使用给定的 [editIdx] 创建 [ExampleAppNodeEdit]。
  ExampleAppNodeEdit({this.editIdx, super.key})
      : assert(editIdx == null || editIdx >= 0);

  [@override](/user/override)
  State<ExampleAppNodeEdit> createState() => _ExampleAppNodeEditState();
}

class _ExampleAppNodeEditState extends State<ExampleAppNodeEdit> {
  /// 控制器用于捕获 [StringNode.ctx]。
  late final TextEditingController _controller;

  [@override](/user/override)
  void initState() {
    super.initState();

    // 获取 StringNode 的上下文。如果索引为空,则返回 null。
    String? txt = widget.editIdx == null
        ? null
        : Provider.of<ListChangeNotifier<StringNode>>(context, listen: false)[widget.editIdx!].ctx;

    // 使用指定的文本构造控制器。
    _controller = TextEditingController(text: txt);
  }

  [@override](/user/override)
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  [@override](/user/override)
  Widget build(BuildContext context) => Scaffold(
      appBar: AppBar(
        title: Text("${widget.editIdx == null ? '添加' : '编辑'}字符串"),
      ),
      body: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            const Text("在这里输入任何文本"),
            // 允许输入任何文本并应用于 StringNode 的文本框。
            Padding(
                padding: const EdgeInsets.all(14),
                child: TextField(
                  controller: _controller,
                )),
            // 保存用户输入文本到 StringNode 的按钮。
            SizedBox(
                width: 120,
                height: 48,
                child: ElevatedButton(
                    onPressed: () {
                      final snl = Provider.of<ListChangeNotifier<StringNode>>(context, listen: false);
                      if (widget.editIdx != null) {
                        // 当给定编辑索引时调用 modify
                        snl.modify(widget.editIdx!, (item) {
                          item.ctx = _controller.text;
                        });
                      } else {
                        // 如果编辑索引为空,则创建新的 StringNode 并添加到 ListChangeNotifier。
                        snl.add(StringNode(_controller.text));
                      }

                      // 应用后弹出当前页面。
                      Navigator.pop(context);
                    },
                    child: Text(widget.editIdx == null ? "添加" : "应用")))
          ]));
}

enum EALOption { 编辑, 删除, 关闭 }

/// 用于列出由 [Provider] 管理的现有 [StringNode] 的小部件。
class ExampleAppList extends StatelessWidget {
  const ExampleAppList({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) {
    // 获取从入口点附加的 ListChangeNotifier。
    ListChangeNotifier<StringNode> snl = Provider.of<ListChangeNotifier<StringNode>>(context);

    return Scaffold(
        appBar: AppBar(title: const Text("已应用的文本")),
        body: ListView.builder(
            itemCount: snl.length,
            itemBuilder: (context, index) => ListTile(
                title: Text(snl[index].ctx),
                // 显示已应用的 StringNode 的选项。
                onLongPress: () async {
                  final EALOption opts = await showDialog<EALOption>(
                          context: context,
                          builder: (context) => SimpleDialog(
                                children: EALOption.values
                                    .map<SimpleDialogOption>((e) =>
                                        SimpleDialogOption(
                                            child: Text(
                                                "${e.name[0].toUpperCase()}${e.name.substring(1)}"),
                                            onPressed: () =>
                                                Navigator.pop<EALOption>(
                                                    context, e)))
                                    .toList(),
                              )) ??
                      EALOption.关闭;

                  switch (opts) {
                    case EALOption.编辑:
                      Navigator.push(
                          context,
                          MaterialPageRoute(
                              builder: (context) => ExampleAppNodeEdit(editIdx: index)));
                      break;
                    case EALOption.删除:
                      snl.removeAt(index);
                      break;
                    case EALOption.关闭:
                      break;
                  }
                })));
  }
}

更多关于Flutter集合变化通知插件collection_change_notifier的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter集合变化通知插件collection_change_notifier的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter项目中使用collection_change_notifier插件的一个简单示例。这个插件允许你监听集合(如List或Set)的变化,并在变化时通知监听者。

首先,确保你已经在pubspec.yaml文件中添加了collection_change_notifier依赖:

dependencies:
  flutter:
    sdk: flutter
  collection_change_notifier: ^x.y.z  # 请替换为最新版本号

然后运行flutter pub get来安装依赖。

接下来,你可以创建一个使用CollectionChangeNotifier的示例。假设我们有一个简单的待办事项应用,我们希望在待办事项列表变化时更新UI。

  1. 创建TodoItem
class TodoItem {
  final String title;
  bool isDone;

  TodoItem({required this.title, this.isDone = false});
}
  1. 创建TodoListNotifier
import 'package:collection_change_notifier/collection_change_notifier.dart';
import 'package:flutter/foundation.dart';

class TodoListNotifier extends ChangeNotifier {
  final CollectionChangeNotifier<TodoItem> _todoItems =
      CollectionChangeNotifier<TodoItem>();

  List<TodoItem> get todoItems => _todoItems.toList();

  void addTodo(TodoItem item) {
    _todoItems.add(item);
    notifyListeners();
  }

  void removeTodo(TodoItem item) {
    _todoItems.remove(item);
    notifyListeners();
  }

  void toggleTodo(TodoItem item) {
    item.isDone = !item.isDone;
    notifyListeners();
  }
}
  1. 在UI中使用TodoListNotifier
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'todo_item.dart';
import 'todo_list_notifier.dart';

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => TodoListNotifier()),
      ],
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Todo App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: TodoListScreen(),
    );
  }
}

class TodoListScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final todoListNotifier = context.watch<TodoListNotifier>();

    return Scaffold(
      appBar: AppBar(
        title: Text('Todo List'),
      ),
      body: ListView.builder(
        itemCount: todoListNotifier.todoItems.length,
        itemBuilder: (context, index) {
          final todoItem = todoListNotifier.todoItems[index];
          return ListTile(
            title: Text(todoItem.title),
            trailing: Checkbox(
              value: todoItem.isDone,
              onChanged: (value) {
                todoListNotifier.toggleTodo(todoItem);
              },
            ),
            onTap: () {
              todoListNotifier.removeTodo(todoItem);
            },
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          final textController = TextEditingController();
          showDialog(
            context: context,
            builder: (context) {
              return AlertDialog(
                title: Text('Add Todo'),
                content: TextField(
                  controller: textController,
                ),
                actions: [
                  TextButton(
                    onPressed: () {
                      Navigator.of(context).pop();
                    },
                    child: Text('Cancel'),
                  ),
                  TextButton(
                    onPressed: () {
                      todoListNotifier.addTodo(TodoItem(title: textController.text));
                      Navigator.of(context).pop();
                    },
                    child: Text('Add'),
                  ),
                ],
              );
            },
          );
        },
        tooltip: 'Add Todo',
        child: Icon(Icons.add),
      ),
    );
  }
}

在这个示例中,我们创建了一个TodoListNotifier类,它使用CollectionChangeNotifier来管理待办事项列表。当待办事项被添加、删除或切换完成时,notifyListeners()方法会被调用,从而通知UI进行更新。

UI部分使用Provider包来提供TodoListNotifier实例,并在TodoListScreen中监听其变化,实时更新待办事项列表。

希望这个示例能帮助你理解如何在Flutter项目中使用collection_change_notifier插件!

回到顶部