Closed JerContact closed 11 months ago
Hey @JerContact
Could you provide a small reproducible example?
it would be very complex to try and reproduce code for this, since it's using bloc and it's fairly imbedded inside a huge project right now.
But my question is really about how can i just reset the treeController so it's not just looking for listener changes, but literally just rebuilds everything from scratch according to the treeMap
The TreeController
doesn't hold anything, so calling TreeController.rebuild()
should always update the trees. If they aren't updating you might be facing another issue. Have you tried giving your node widgets a key?
Each node has a key for my treeMap, let's say i have this
TreeMap has 2 entries [0] - root nodes with 4 items [1] - child with key 16 (the id of the 4th root node) and there are 2 items in this
I then totally replace the root nodes with a re-ordered version, everything else stays the same (the child still remains in the treemap)
when the treecontroller rebuilds it only rebuilds the roots and doesn't seem to pay attention to the children, even though they are in the treeMap
It's weird, in the node builder, it builds less nodes as well, and if I move the roots back into their old order by replacing the root nodes again, it all appears again....
I'm afraid I may not be able to help you without looking at the source code.
I have no clue of what the issue might be.
I believe this is a similar example, just need to replace "lazy_loading.dart" in your example project with this
import 'dart:math' show Random;
import 'package:flutter/material.dart';
import 'package:flutter_fancy_tree_view/flutter_fancy_tree_view.dart';
import '../shared.dart' show watchAnimationDurationSetting;
class Data {
static const Data root = Data._root();
const Data._root()
: id = 0,
title = '/';
// static int _uniqueId = 1;
Data(this.title, this.id);
final int id;
final String title;
}
int uniqueId = 4;
int _generateUniqueId() => uniqueId++;
class LazyLoadingTreeView extends StatefulWidget {
const LazyLoadingTreeView({super.key});
@override
State<LazyLoadingTreeView> createState() => _LazyLoadingTreeViewState();
}
class _LazyLoadingTreeViewState extends State<LazyLoadingTreeView> {
late final Random rng = Random();
late final TreeController<Data> treeController;
Iterable<Data> childrenProvider(Data data) {
return childrenMap[data.id] ?? const Iterable.empty();
}
final Map<int, List<Data>> childrenMap = {
Data.root.id: [Data('One', 1), Data('Two', 2), Data('Three', 3)],
};
final Set<int> loadingIds = {};
Future<void> loadChildren(Data data) async {
final List<Data>? children = childrenMap[data.id];
if (children != null) return;
setState(() {
loadingIds.add(data.id);
});
await Future.delayed(const Duration(milliseconds: 750));
childrenMap[data.id] = List.generate(
rng.nextInt(4) + rng.nextInt(1),
(_) => Data('Node', _generateUniqueId()),
);
loadingIds.remove(data.id);
if (mounted) setState(() {});
treeController.expand(data);
}
Widget getLeadingFor(Data data) {
if (loadingIds.contains(data.id)) {
return const Center(
child: SizedBox.square(
dimension: 20,
child: CircularProgressIndicator(strokeWidth: 2),
),
);
}
late final VoidCallback? onPressed;
late final bool? isOpen;
final List<Data>? children = childrenMap[data.id];
if (children == null) {
isOpen = false;
onPressed = () => loadChildren(data);
} else if (children.isEmpty) {
isOpen = null;
onPressed = null;
} else {
//isOpen = treeController.getExpansionState(data);
isOpen = true;
onPressed = () => treeController.toggleExpansion(data);
}
return FolderButton(
key: GlobalObjectKey(data.id),
isOpen: isOpen,
onPressed: onPressed,
);
}
@override
void initState() {
super.initState();
treeController = TreeController<Data>(
roots: childrenProvider(Data.root),
childrenProvider: childrenProvider,
);
}
@override
void dispose() {
treeController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Expanded(
child: AnimatedTreeView<Data>(
treeController: treeController,
nodeBuilder: (_, TreeEntry<Data> entry) {
return TreeIndentation(
entry: entry,
child: Row(
children: [
SizedBox.square(
dimension: 40,
child: getLeadingFor(entry.node),
),
Text(entry.node.title),
],
),
);
},
duration: watchAnimationDurationSetting(context),
),
),
ElevatedButton(
onPressed: () {
setState(() {
final test = [
Data('Three', 3),
Data('One', 1),
Data('Two', 2),
];
treeController.roots = test;
});
},
child: const Text('Change Order')),
],
);
}
}
To Repro:
Just looking at the example, I noticed that you are creating new Data
instances with the same id, but the Data
class doesn't override the == operator... the TreeController
uses a Set
data structure to store the expansion state of tree nodes. Without overriding the == operator, those nodes would not match (Set.contains
would return false even though the ids match), loosing the expansion state between rebuilds.
It's pretty late here. I will take a deeper look at this tomorrow. Let me know if overriding the == operator solves your issue.
Ahhhhh, the == does seem to fix this issue on the example, let me try my project and get back to you, thanks for getting back!!
i guess my problem is i'm essentially replacing the whole tree since it can get really complicated when moving multiple nodes around from all over the tree view, so I think it's literally breaking the functionality of the treeController....is there a way the treeController could just start from scratch each time a rebuild happens? But, keep it's heirachy and which nodes are open and connected to each other?
I might have to just fork a change to do it, but any help to guide me where i would make these changes would be super helpful!
Hey, sorry for the delayed response.
I'm pretty sure just setting treeController.roots = newRoots
should do the trick, as long as your tree nodes have consistent ==
and hashCode
implementations so you keep the expansion state upon tree refresh.
Keeping the nodes hierarchy and relationships is up to you as there are many ways to represent a tree and I didn't want to force all users to depend on a mandatory implementation.
I'll close this issue for now but feel free to reopen it. :blush:
I'm implementing a drag and drop, and the treeController won't update properly when moving around nodes, is there a way to reboot the treeController to just rebuild everything? All the data looks right in the treeMap, but when the treeController builds, it just doesn't seem the adjust nodes for some reason.
This happens a lot when having nodes expanded and then dragging that parent around and asking the treeController to update.