Type mismatch on generic Nodes

Issue #32 open
Gustav Gussi created an issue

You use generic Node<T> but you never address this T type in your code. That is why sometimes TypeError is occurred.

For example I have a function:

void _onNodeSelected(Node<Zone> node) {
 ...
}

When this code is executing

final node = controller.getNode('key');
_onNodeSelected(node); // Crash is here because Node<Zone> is expected but Node<dynamic> is provided.

Please respect the type T everywhere where Node type is used, i.e. Node<T> instead of Node.

P.S.: Also you created `nodeBuilder` (thanks for that!) but you use

Function (BuildContext context, Node node)

and it is better to use:

Widget Function (BuildContext context, Node node)

Comments (15)

  1. Kevin Armstrong

    Hi Gustav, The T type is addressed in the place where it is defined: on the Node class. In order to use Node<T> throughout the code, I would need to have and define T throughout the code. That isn’t feasible.

    If you define your type Node<Zone>, not just Node, when you create the node, getNode will return Node<Zone> and not Node<dynamic>.

  2. Gustav Gussi reporter

    If you define your type Node<Zone>, not just Node, when you create the node, getNode will return Node<Zone> and not Node<dynamic>.

    Unfortunately it is not true. 😞 And also `copyWith` returns Node<dynamic> instead of Node<Zone> but I clonned Node<Zone>.

  3. Kevin Armstrong

    Gustav, can you provide me with a code sample where this is not true for you. I am willing to make the changes.

  4. Gustav Gussi reporter

    Kevin, unfortunately I cannot copy my code as I work via VPN (w/o Internet) so I can just provide some lines from my real code:

    // Somewhere in StreamBuilder's builder:
    
    final zones = snapshot.data.items; // zones is a List<Zone>
    final treeNodes = _buildTree(zones); // treeNodes is a List<Node<Zone>>
    treeController = TreeViewController(
      children: treeNodes, // Here children already a List<Node<dynamic>>
      selectedKey: _selectedKey,
    );
    
    return TreeView(
      controller: treeControoller,
      ...
      onNodeTap: _nodeSelectHandler,
    );
    
    // Node selection handler
    Future<void> _nodeSelectHandler(String key) async {
      final node = treeController.getNode(key); // already here `node` is Node<dynamic>
      final updatedZone = await zoneBloc.getEntity((node.data as Zone).id);
      widget.onNodeChanged?.call(node.cast(updateZone)); // Here I am forced to call a `cast` method. Otherwise I have a TypeError because the `onNodeChanged` is ValueChanged<Node<Zone>>
    }
    
    //
    extension NodeExtension<T> on Node<T> {
      Node<T> cast([T data]) => Node<T>(
        key: key,
        label: label,
        ...
        data: data ?? this.data,
      );
    }
    

  5. 吕彦

    Please respect the type T everywhere where Node type is used, i.e. Node<T> instead of Node.

    I also agree with that

  6. Jennie Browne

    Also having this issue when using copyWith it returns Node<dynamic> rather than Node<T>. Please add all generic types as in the example…

    Please change this

    Node<T> copyWith({
      String? key,
      String? label,
      List<Node<T>>? children,
      bool? expanded,
      bool? parent,
      IconData? icon,
      T? data,
    }) =>
        Node<T>(
          key: key ?? this.key,
          label: label ?? this.label,
          icon: icon ?? this.icon,
          expanded: expanded ?? this.expanded,
          parent: parent ?? this.parent,
          children: children ?? this.children,
          data: data ?? this.data,
        );
    

  7. Jennie Browne

    Thanks so much Kevin. While you are in the TreeViewController class…there were quite a few other references that return Node rather than Node<T> if you can fix those too?

    Thanks again!!

  8. Kevin Armstrong

    I moved too fast Jennie. I already published those updates.

    I think I mentioned this previously but unless I’m missing something, there is no way for the TreeViewController to know the generic T so I can’t add it throughout without providing a means for you to pass that generic at method invocation. e.g.

    var node = controller.getNode<double>(key);
    

  9. Jennie Browne

    Hi Kevin,

    You can pass T through on the method. If you are creating a Node without a type (such as copyWith()) inside TreeViewController it defeats the purpose of a strongly typed Node.

    For example in copyWith you could change the method signature to →

    Node copyWith<T>({
    })
    

    And then you call it with:

    copyWith<String>();
    

    If you are calling it within TreeViewController you need to make sure all method sigs pass through the type T. For example for updateNode it would look like this:

    List<Node<T>> updateNode<T>(String key, Node<T> newNode, {Node<T>? parent}) {
      List<Node<T>> _children = parent == null ? this.children : parent.children;
      return _children.map((Node<T> child) {
        if (child.key == key) {
          return newNode;
        } else {
          if (child.isParent) {
            return child.copyWith<T>(
              children: updateNode<T>(
                key,
                newNode,
                parent: child,
              ),
            );
          }
          return child;
        }
      }).toList();
    

    The root issue I am trying to address it when we lose the typed Node during a copyWith (basically where you created a Node() without a type.)

    Thanks Kevin!

  10. Kevin Armstrong

    Thanks Jennie, that certainly clears up what Gustav was trying to convey. And it’s something I didn’t think about when I added the data property. I will work on getting that updated ASAP.

  11. Log in to comment