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
[MenuAnchor] needs simpler example #148104
Comments
Thanks for your proposal. I think we can add this as a new example on API docs. Please feel free to create a PR for this. |
Sure 😊 |
On the same note - MenuAnchor is supposed to an updated PopupMenuButton I'd also like to consider a case of sharing the same visual menu mapped to different callbacks. For instance a right click menu whose functions are relative the object clicked. I came up with the following solution, essentially an implementation of the flyweight pattern // MenuSource<T> is a flyweight factory, where the menu items are shared across instances
// either MenuItemButton callback layer indirection via context - build time
// or instances use shallow copy - init time
class MenuSource<T> {
MenuSource._({required this.menuItems});
MenuSource._instance(MenuSource<T> menuSource) : menuItems = menuSource.menuItems;
const MenuSource.items(List<MenuSourceItem> this.menuItems);
MenuSource.itemBuilder({
required Iterable<T> itemKeys,
required Widget Function(T) itemBuilder,
ValueSetter<T>? onPressed,
void Function(BuildContext context, T newValue, T oldValue)? onPressedExt,
}) : menuItems = [
for (final key in itemKeys)
MenuSourceItem<T>(
itemKey: key,
onPressed: onPressed,
onPressedExt: onPressedExt,
menuItemButton: MenuItemButton(child: itemBuilder(key)),
),
];
final List<Widget> menuItems;
MenuSourceInstance<T> instance() => MenuSourceInstance(this);
}
// alternatively shallow copy
class MenuSourceInstance<T> extends MenuSource<T> {
MenuSourceInstance(super.menuSource) : super._instance();
ValueNotifier<T?> notifier = ValueNotifier<T?>(null);
}
// wrapper around MenuItemButton, to allow for a shared List<MenuItemButton> across instances
// use the same data as MenuItemButton, replacing onPressed with a callback to the notifier
// build time copy allows menuItemButton to be shared, alternatively use copyWith to create a shallow copy per instance
class MenuSourceItem<T> extends StatelessWidget {
const MenuSourceItem({super.key, required this.menuItemButton, required this.itemKey, this.onPressed, this.onPressedExt});
final MenuItemButton menuItemButton;
final ValueSetter<T>? onPressed;
final void Function(BuildContext context, T newValue, T? oldValue)? onPressedExt;
final T itemKey;
@override
Widget build(BuildContext context) {
final notifier = MenuSourceContext.of<T>(context);
return MenuItemButton(
onPressed: () {
onPressed?.call(itemKey);
onPressedExt?.call(context, itemKey, notifier.value);
notifier.value = itemKey;
},
child: menuItemButton.child,
);
}
}
// Although MenuSource generally controls only 1 MenuListenableWidget, maps 1:1, InheritedNotifier simplifies implementation.
class MenuSourceContext<T> extends InheritedNotifier<ValueNotifier<T?>> {
const MenuSourceContext._({super.key, required ValueNotifier<T?> super.notifier, required super.child});
MenuSourceContext({super.key, required MenuSourceInstance<T?> source, required super.child}) : super(notifier: source.notifier);
static ValueNotifier<T?> of<T>(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<MenuSourceContext<T>>()!.notifier!;
}
}
class MenuSourceButton<T> extends StatelessWidget {
const MenuSourceButton({super.key, required this.source});
final MenuSourceInstance<T> source;
@override
Widget build(BuildContext context) {
return MenuSourceContext<T>(
source: source,
child: MenuAnchorButton(items: source.menuItems),
);
}
}
// Menu 'hosts' must wrap MenuAnchor under MenuSourceContext, to allow for the notifier to be accessed by the menu items
class MenuSourceWidget<T> extends StatelessWidget {
const MenuSourceWidget({super.key, required this.source, this.child, required this.builder});
final MenuSourceInstance<T> source;
final ValueWidgetBuilder<T?> builder;
final Widget? child;
@override
Widget build(BuildContext context) {
// menuItems onPressed will find the notifier from MenuSourceInstance
return MenuSourceContext<T>(
source: source,
// "Dependents are notified whenever the notifier sends notifications, or whenever the identity of the notifier changes."
// not working without ValueListenableBuilder?
child: MenuAnchorOverlay(
items: source.menuItems,
child: ValueListenableBuilder<T?>(valueListenable: source.notifier, builder: builder, child: child),
),
);
}
}
// case where child depends on menu without displaying the menu
class MenuListenableBuilder<T> extends StatelessWidget {
const MenuListenableBuilder({super.key, required this.builder, required this.source, this.child});
final MenuSourceInstance<T> source;
final ValueWidgetBuilder<T?> builder;
final Widget? child;
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<T?>(valueListenable: source.notifier, builder: builder, child: child);
}
}
class MenuSourceTheme extends ThemeExtension<MenuSourceTheme> {
const MenuSourceTheme({this.trailingImage});
final ImageProvider? trailingImage;
@override
ThemeExtension<MenuSourceTheme> copyWith() {
throw UnimplementedError();
}
@override
ThemeExtension<MenuSourceTheme> lerp(covariant ThemeExtension<MenuSourceTheme>? other, double t) {
throw UnimplementedError();
}
}
|
Use case
The current MenuAnchor example is very comprehensive, whereas for a beginner like me, it is very complicated. Therefore, I think more developers would use MenuAnchor if there were simpler and less time-consuming examples to understand.
Proposal
Eliminated shortcuts, enums, etc., resulting in less code.
The text was updated successfully, but these errors were encountered: