You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于Firestore子集合实现带实时更新的Animated Tree View

基于Firestore实现Animated Tree View实时更新方案

核心思路

要实现Firestore数据变化时树形视图自动更新,需要完成三个关键步骤:Firestore实时数据监听、数据与Tree节点的同步转换、触发Flutter UI刷新。以下是具体实现方案:


1. 定义节点数据模型

继承TreeNode封装Firestore数据结构,方便后续树结构构建:

import 'package:animated_tree_view/animated_tree_view.dart';
import 'package:cloud_firestore/cloud_firestore.dart';

class ActivityNode extends TreeNode {
  final String id;
  final String parentId;
  final String title;

  ActivityNode({
    required this.id,
    required this.parentId,
    required this.title,
    List<ActivityNode>? children,
  }) : super(
          key: id,
          children: children ?? [],
        );

  // 从Firestore文档转换为节点对象
  factory ActivityNode.fromFirestore(DocumentSnapshot doc) {
    final data = doc.data() as Map<String, dynamic>;
    return ActivityNode(
      id: doc.id,
      parentId: data['parentId'],
      title: data['title'],
    );
  }
}

2. 改造页面State逻辑

在页面State类中添加Firestream监听、树结构维护逻辑:

class _TreePageState extends State<TreePage> {
  final _scaffoldKey = GlobalKey<ScaffoldState>();
  final scrollController = ScrollController();
  final _simpleTreeKey = GlobalKey<SliverTreeViewState>();
  late Tree<ActivityNode> simpleTree;
  StreamSubscription? _firestoreSubscription;

  @override
  void initState() {
    super.initState();
    // 初始化根节点
    simpleTree = Tree.root(ActivityNode(id: 'root', parentId: '', title: 'Root'));
    // 启动Firestore实时监听
    _startFirestoreListener();
  }

  // 监听Firestore集合的实时变化
  void _startFirestoreListener() {
    final stream = FirebaseFirestore.instance.collection('activities').snapshots();
    _firestoreSubscription = stream.listen(_handleFirestoreUpdates);
  }

  // 处理Firestore数据变化,同步更新树结构
  void _handleFirestoreUpdates(QuerySnapshot snapshot) {
    final nodeMap = <String, ActivityNode>{};
    // 先将所有文档转为节点并存入Map
    for (var doc in snapshot.docs) {
      final node = ActivityNode.fromFirestore(doc);
      nodeMap[node.id] = node;
    }

    // 重建树结构:清空根节点子项,重新关联父节点
    simpleTree.root.children.clear();
    for (var node in nodeMap.values) {
      final parentNode = nodeMap[node.parentId] ?? simpleTree.root;
      if (!parentNode.children.contains(node)) {
        parentNode.add(node);
      }
    }

    // 触发UI刷新,Animated Tree View会自动播放动画
    setState(() {});
  }

  @override
  void dispose() {
    // 取消监听,避免内存泄漏
    _firestoreSubscription?.cancel();
    scrollController.dispose();
    super.dispose();
  }

  // 原build方法调整节点渲染逻辑
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: _scaffoldKey,
      appBar: AppBar(title: Text(widget.title)),
      body: CustomScrollView(
        controller: scrollController,
        slivers: [
          SliverToBoxAdapter(
            child: Padding(
              padding: const EdgeInsets.only(top: 12.0, left: 12, right: 30, bottom: 12),
              child: Align(
                alignment: Alignment.topLeft,
                child: SizedBox(
                  width: 200,
                  height: 40,
                  child: ElevatedButton(
                    child: const Text("Add main activity"),
                    style: ElevatedButton.styleFrom(
                      backgroundColor: Theme.of(context).primaryColor,
                      textStyle: const TextStyle(color: Colors.white),
                      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)),
                    ),
                    onPressed: () async {
                      final result = await Navigator.of(context).push(
                        MaterialPageRoute(
                          builder: (context) => const ActivityAdd(title: 'Add Activity', selfId: 'root'),
                        ),
                      );
                      // 无需手动调用_getRequests,实时监听会自动同步数据
                    },
                  ),
                ),
              ),
            ),
          ),
          SliverTreeView.simple(
            tree: simpleTree,
            expansionBehavior: ExpansionBehavior.collapseOthers,
            showRootNode: false,
            key: _simpleTreeKey,
            scrollController: scrollController,
            builder: (context, node) {
              final activityNode = node as ActivityNode;
              return Card(
                child: ListTile(
                  title: Text(activityNode.title),
                  trailing: node.hasChildren
                      ? Icon(node.isExpanded ? Icons.expand_less : Icons.expand_more)
                      : null,
                  onTap: () => node.toggleExpanded(),
                ),
              );
            },
          ),
        ],
      ),
    );
  }
}

3. 优化建议(大数据量场景)

如果节点数量较多,全量重建树结构会影响性能,可以改为仅更新变化的节点:

void _handleFirestoreUpdates(QuerySnapshot snapshot) {
  for (var change in snapshot.docChanges) {
    final node = ActivityNode.fromFirestore(change.doc);
    switch (change.type) {
      case DocumentChangeType.added:
        // 新增节点:找到父节点并添加
        final parent = simpleTree.findNode(node.parentId) ?? simpleTree.root;
        parent.add(node);
        break;
      case DocumentChangeType.modified:
        // 修改节点:替换原有节点
        final existingNode = simpleTree.findNode(node.id);
        if (existingNode != null) {
          existingNode.parent?.replace(existingNode, node);
        }
        break;
      case DocumentChangeType.removed:
        // 删除节点:找到节点并移除
        final existingNode = simpleTree.findNode(node.id);
        existingNode?.parent?.remove(existingNode);
        break;
    }
  }
  setState(() {});
}

关键注意事项

  • Firestore数据结构推荐使用扁平化集合:所有节点存于同一个集合,通过parentId字段关联父节点,避免递归监听子集合的复杂度。
  • 确保Firestore安全规则允许对activities集合的读写权限。
  • 调用setState()时,Animated Tree View会自动处理节点展开/折叠的动画效果,无需额外配置。

内容的提问来源于stack exchange,提问作者Dev Conductor

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 15:27:49