Skip to content

MenuItemButton may throw when updated with a null FocusNode #142095

@davidhicks980

Description

@davidhicks980

On the MenuItemButton widget, if widget.focusNode is updated to null, didUpdateWidget would throw.

Steps to reproduce

Change menu item focus node to null

Expected outcome

Doesn't throw

Actual outcome

Throws

Code sample
import 'package:flutter/material.dart';

/// Flutter code sample for [MenuAnchor].

void main() => runApp(const MenuApp());

class MenuApp extends StatelessWidget {
  const MenuApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(useMaterial3: true),
      home: const Scaffold(body: SafeArea(child: MyCascadingMenu())),
    );
  }
}

class MyCascadingMenu extends StatefulWidget {
  const MyCascadingMenu({super.key});

  @override
  State<MyCascadingMenu> createState() => _MyCascadingMenuState();
}

class _MyCascadingMenuState extends State<MyCascadingMenu> {
  late FocusNode? _buttonFocusNode = FocusNode();

  @override
  Widget build(BuildContext context) {
    return MenuAnchor(
      menuChildren: <Widget>[
        MenuItemButton(
            focusNode: _buttonFocusNode,
            closeOnActivate: false,
            child: Text("Set focus to null"),
            onPressed: () => setState(() {
                  _buttonFocusNode = null;
                })),
      ],
      builder:
          (BuildContext context, MenuController controller, Widget? child) {
        return TextButton(
          onPressed: () {
            if (controller.isOpen) {
              controller.close();
            } else {
              controller.open();
            }
          },
          child: const Text('OPEN MENU'),
        );
      },
    );
  }
}

Here is a gist demonstrating the bug in action on the Master branch: https://dartpad.dev/?id=a7da3f81557b11f7080b1f5a8afa0779&channel=master

Here is the code in the master material library: https://github.com/flutter/flutter/blob/b5262f0d80eb6da1b1cd6ceff0943367fbc29331/packages/flutter/lib/src/material/menu_anchor.dart#L1078C8-L1078C53

The code throws on both master and stable branches.

Relevate framework code
class _MenuItemButtonState extends State<MenuItemButton> {
  FocusNode? _internalFocusNode;
  FocusNode get _focusNode => widget.focusNode ?? _internalFocusNode!;

  // ...

  @override
  void didUpdateWidget(MenuItemButton oldWidget) {
    if (widget.focusNode != oldWidget.focusNode) {
      // **BUG?** If widget.focusNode changes to null, this appears to throw
      _focusNode.removeListener(_handleFocusChange);
      if (widget.focusNode != null) {
        _internalFocusNode?.dispose();
        _internalFocusNode = null;
      }
      _createInternalFocusNodeIfNeeded();
      _focusNode.addListener(_handleFocusChange);
    }
    super.didUpdateWidget(oldWidget);
  }

  // ...

  void _createInternalFocusNodeIfNeeded() {
    if (widget.focusNode == null) {
      _internalFocusNode = FocusNode();
      assert(() {
        if (_internalFocusNode != null) {
          _internalFocusNode!.debugLabel = '$MenuItemButton(${widget.child})';
        }
        return true;
      }());
    }
  }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    a: error messageError messages from the Flutter frameworkf: material designflutter/packages/flutter/material repository.found in release: 3.16Found to occur in 3.16found in release: 3.19Found to occur in 3.19frameworkflutter/packages/flutter repository. See also f: labels.has reproducible stepsThe issue has been confirmed reproducible and is ready to work onr: fixedIssue is closed as already fixed in a newer versionteam-designOwned by Design Languages team

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions