Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix SearchAnchor disposing SearchController while it is still used #155219

Open
wants to merge 22 commits into
base: master
Choose a base branch
from

Conversation

PurplePolyhedron
Copy link
Contributor

@PurplePolyhedron PurplePolyhedron commented Sep 15, 2024

fixes #155180
New behaviour: SearchAnchor now closes the menu when itself is disposed while the menu is still open. This is the behaviour of MenuAnchor/OverlayPortal.

Pre-launch Checklist

If you need help, consider asking for advice on the #hackers-new channel on Discord.

@github-actions github-actions bot added framework flutter/packages/flutter repository. See also f: labels. f: material design flutter/packages/flutter/material repository. labels Sep 15, 2024
@PurplePolyhedron PurplePolyhedron marked this pull request as ready for review September 16, 2024 06:11
@PurplePolyhedron
Copy link
Contributor Author

I moved the disposal from _SearchViewRoute to _ViewContentState and added more comments.

Copy link
Member

@nate-thegrate nate-thegrate left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm really impressed with how the search anchor has been updated to elegantly handle any combination of the search view route & search anchor being created/disposed.

My main concern is the concept of "spaghetti code": adding a few extra members isn't a huge deal but does make the API slightly more difficult to understand & maintain.


My current inclination is to wait a few days to see if anyone has ideas for improving readability; if not, I'd be happy to approve this 🙂

packages/flutter/lib/src/material/search_anchor.dart Outdated Show resolved Hide resolved
packages/flutter/test/material/search_anchor_test.dart Outdated Show resolved Hide resolved
packages/flutter/test/material/search_anchor_test.dart Outdated Show resolved Hide resolved
packages/flutter/test/material/search_anchor_test.dart Outdated Show resolved Hide resolved
packages/flutter/test/material/search_anchor_test.dart Outdated Show resolved Hide resolved
Copy link
Member

@nate-thegrate nate-thegrate left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the updates!


I think I figured out a way to use 1 boolean flag instead of 2:

class SearchController {
  // If set to true, the SearchAnchor or ViewContent should
  // dispose of the controller when it's no longer in use.
  bool _internal = false;
}

class _SearchAnchorState {
  SearchController get _searchController {
    return widget.searchController
        ?? (_internalSearchController ??= SearchController().._internal = true);
  }

  void dispose() {
    widget.searchController?._detach(this);
    _internalSearchController?._detach(this);

    final _SearchViewRoute? route = _route;
    if (route != null && route.isActive) {
      route.navigator!.removeRoute(route);
    } else {
      _internalSearchController?.dispose();
    }
    super.dispose();
  }
}


class _ViewContentState {
  void dispose() {
    if (_controller._internal && !_controller.isAttached) {
      _controller.dispose();
    }
  }
}

Let me know what you think!

@PurplePolyhedron
Copy link
Contributor Author

PurplePolyhedron commented Sep 18, 2024

Thanks for the updates!

I think I figured out a way to use 1 boolean flag instead of 2:

class SearchController {
  // If set to true, the SearchAnchor or ViewContent should
  // dispose of the controller when it's no longer in use.
  bool _internal = false;
}

class _SearchAnchorState {
  SearchController get _searchController {
    return widget.searchController
        ?? (_internalSearchController ??= SearchController().._internal = true);
  }

  void dispose() {
    widget.searchController?._detach(this);
    _internalSearchController?._detach(this);

    final _SearchViewRoute? route = _route;
    if (route != null && route.isActive) {
      route.navigator!.removeRoute(route);
    } else {
      _internalSearchController?.dispose();
    }
    super.dispose();
  }
}


class _ViewContentState {
  void dispose() {
    if (_controller._internal && !_controller.isAttached) {
      _controller.dispose();
    }
  }
}

Let me know what you think!

Thanks for the suggestion, I tried it but it crashes using the example from the issue. My bad for not covering this in the test cases. I've now added a new test for that.
I think the crash is due to the route is considered not isActive during the exit animation.

However it did inspired me to come up with a new idea.

Copy link
Member

@nate-thegrate nate-thegrate left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks fantastic—I like this current setup a lot more than anything suggested previously!

I believe only one more change is needed:

  @override
  void didUpdateWidget(SearchAnchor oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.searchController != widget.searchController) {
      oldWidget.searchController?._detach(this);
      _searchController._attach(this);
    }
  }

Based on that method, it looks like there's an edge case where the SearchAnchor starts using the internal search controller, but later switches over to an external one—in that case, the internal controller would need to be disposed of.

Comment on lines +407 to +409
_route?._dismiss(
disposeController: widget.searchController == null,
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
_route?._dismiss(
disposeController: widget.searchController == null,
);
final bool usingExternalController = widget.searchController != null;
_route?._dismiss(disposeController: !usingExternalController);
if (usingExternalController) {
_internalSearchController?.dispose();
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

SearchAnchor tries to use its controller after being disposed
3 participants