baumths / flutter_tree_view

A Flutter collection of widgets and slivers that helps bringing your hierarchical data to life.
https://baumths.github.io/flutter_tree_view
MIT License
176 stars 63 forks source link

How to use provider (riverpod) in the childrenProvider #51

Closed 5hirish closed 1 year ago

5hirish commented 1 year ago

I have a riverpod StateNotifierProvider reading files from a directory asynchronously. I would like to use it in my tree controller to to provide the child nodes which will be fetched onPressed. I am unable to read a provider in the childrenProvider as it throws a StackOverflow error.

The AnimatedTreeView is contained in a HookConsumerWidget. Reason behind using hooks is to maintain a state of the widget tree's expansion.

final treeController = useState(TreeController<FileSystemEntity>(
    roots: roots,
    childrenProvider: (FileSystemEntity parent) => ref.read(projectFilesProvider.notifier).getProjectRootFiles() ?? const Iterable.empty(),
  ));

Stack Trace:

The following StackOverflowError was thrown building SizeTransition(animation: AnimationController#2f3bb(▶ 1.000)➩CurveTween(curve: _Linear)➩1.0, state: _AnimatedState#c9986):
Stack Overflow

When the exception was thrown, this was the stack: 
#0      ProviderContainer.readProviderElement.<anonymous closure>.<anonymous closure> (package:riverpod/src/framework/container.dart:445:40)
#1      _HashMapKeyIterable.forEach.<anonymous closure> (dart:collection-patch/collection_patch.dart:491:13)
#2      _HashMap.forEach (dart:collection-patch/collection_patch.dart:153:15)
#3      ProviderElementBase.visitAncestors (package:riverpod/src/framework/element.dart:849:24)
#4      ProviderContainer.readProviderElement.<anonymous closure> (package:riverpod/src/framework/container.dart:445:25)
#5      ProviderContainer.readProviderElement (package:riverpod/src/framework/container.dart:477:8)
#6      ProviderElementProxy.read (package:riverpod/src/framework/proxy_provider_listenable.dart:114:26)
#7      useTreeControllerForFileSystemEntities.<anonymous closure> (package:quinine/hooks/tree.dart:29:56)
#8      TreeController.depthFirstTraversal.createTreeEntriesRecursively (package:flutter_fancy_tree_view/src/tree_controller.dart:386:54)
...
#1893   TreeController.depthFirstTraversal.createTreeEntriesRecursively (package:flutter_fancy_tree_view/src/tree_controller.dart:399:11)
#1894   TreeController.depthFirstTraversal (package:flutter_fancy_tree_view/src/tree_controller.dart:411:7)
#1895   _SliverAnimatedTreeState._buildSubtree (package:flutter_fancy_tree_view/src/sliver_animated_tree.dart:197:23)
#1896   _SubtreeState.initState (package:flutter_fancy_tree_view/src/sliver_animated_tree.dart:411:45)
#1897   StatefulElement._firstBuild (package:flutter/src/widgets/framework.dart:5101:55)
#1898   ComponentElement.mount (package:flutter/src/widgets/framework.dart:4944:5)
...     Normal element mounting (7 frames)
#1905   Element.inflateWidget (package:flutter/src/widgets/framework.dart:3953:16)
#1906   Element.updateChild (package:flutter/src/widgets/framework.dart:3676:20)
#1907   ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4993:16)
#1908   StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5133:11)
#1909   Element.rebuild (package:flutter/src/widgets/framework.dart:4690:5)
#1910   BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2743:19)
#1911   WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:863:21)
#1912   RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:381:5)
#1913   SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1289:15)
#1914   SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1218:9)
#1915   SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:1076:5)
#1916   _invoke (dart:ui/hooks.dart:145:13)
#1917   PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:338:5)
#1918   _drawFrame (dart:ui/hooks.dart:112:31)
baumths commented 1 year ago

Hey @5hirish!

Please do not confuse the childrenProvider callback with the providers of package:provider or package:riverpod. The childrenProvider callback should behave like a getter as it is called recursively while building the tree, you may do the following:

// Access the children directly
(FileSystemEntity parent) => parent.children;

// Or with a map (the key could also be the id of your nodes)
// The below map could be held by a StateProvider if desired
Map<FileSystemEntity, List<FileSystemEntity>> childrenCache;

(FileSystemEntity parent) => childrenCache[parent] ?? const Iterable.empty();

I don't use package:riverpod nor package:flutter_hooks so I wouldn't be capable of helping you out if the issue turns out to be with reading providers in a recursive function.

5hirish commented 1 year ago

Interesting so, I will have to store this tree in-memory ? I am actually changing the visibility of this tree based on a toggle but the expanded state wasn't being maintained in the tree. Hence, I was planning to use providers (in my case with riverpod) to store the expanded state even if the widget is recreated on toggling visibility. Any suggestions?

Because I am also using getter but a provider getter, which is fine. The error is more directed in open transition. The collapse transition seems, while collapsing I can see the files. Only on expansion is the error I get.

The following StackOverflowError was thrown building SizeTransition(animation: AnimationController#c4e62(▶ 0.944)➩CurveTween(curve: _Linear)➩0.9444433333333334, state: _AnimatedState#9f563):
Stack Overflow
baumths commented 1 year ago

I'm not sure what is causing the stack overflow error, but if all you need is to preserve the expansion state of your nodes, you can override the [get|set]ExpansionState methods of the TreeController class:

class MyTreeController extends TreeController<T> {
  TreeController({
    // ...
    Iterable<Object>? initiallyExpandedNodes,
  }) : expandedNodes = {...?initiallyExpandedNodes};

  final Set<Object> expandedNodes;

  @override
  bool getExpansionState(T node) {
    return expandedNodes.contains(node.id);
  }

  @override
  void setExpansionState(T node, bool expanded) {
    if (expanded) {
      expandedNodes.add(node.id);
    } else {
      expandedNodes.remove(node.id);
    }
  }
}

With the above implementation you could approach preserving the expansion state by:

5hirish commented 1 year ago

There is an inconsistent behaviour here too. Where toggling the widget containing the tree in and out of visibility leads to an inconsistent behaviour. After expansion trigger the rebuilding doesn't happen on expansion and the children are not displayed.

isOpen = _treeController.getExpansionState(file);
onPressed = () => _treeController.toggleExpansion(file);
baumths commented 1 year ago

Could you provide a a minimal reproducible example of the issue?

5hirish commented 1 year ago

@baumths just try toggling the visibility couple of times and expanding few nodes, you will encounter a scenario where any node doesn't expand. 5hirish/fancy_tree_example. Thanks

I takes me about 4-5 tries to get it to fail.

5hirish commented 1 year ago

I haven't added the logic to retain the expanded state after widget rebuild on this yet. I wanted to first investigate the inconsistency before any enhancements.

baumths commented 1 year ago

Should be fixed in https://github.com/5hirish/fancy_tree_example/pull/1.

By the way, this does not seem to be an issue with this library. As my knowledge of package:riverpod and package:flutter_hooks is very limited, I wont be able to assist you any further. Hope my PR helps you towards the desired implementation.

I will close this issue for now but feel free to reopen it if you find out there's actually an issue with this library.

5hirish commented 1 year ago

Oh perfect I understand the mistake. Thanks a bunch.

I am building an open-source low-code Flutter IDE.

A low-code, low-barrier-to-entry IDE which focuses on productivity by simple drag-n-drop, plug-n-play interface while at the same time offering the control and freedom to build production-grade apps at scale.

Would love to talk about it if you are interested!

5hirish commented 1 year ago

@baumths I made a little YouTube live code stream tutorial with your library! Hope it helps for folks who want to use the library! Flutter Code Along: How to display files in a tree | Tonic IDE | EP #1