import 'package:flutter/material.dart';
void main() => runApp(const CustomContextMenuBuilderIssue());
class CustomContextMenuBuilderIssue extends StatefulWidget {
const CustomContextMenuBuilderIssue({super.key});
@override
State<CustomContextMenuBuilderIssue> createState() => _CustomContextMenuBuilderIssueState();
}
class _CustomContextMenuBuilderIssueState extends State<CustomContextMenuBuilderIssue> {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Custom contextMenuBuilder')),
body: const Center(child: TheButton()),
),
);
}
}
class TheButton extends StatefulWidget {
const TheButton({super.key});
@override
State<TheButton> createState() => _TheButtonState();
}
class _TheButtonState extends State<TheButton> with TickerProviderStateMixin {
late final _animationController =
AnimationController(duration: const Duration(milliseconds: 300), vsync: this);
bool _openInProgress = false;
OverlayEntry? _popup;
late ThemeData _theme;
String _value = '0.0';
@override
void didChangeDependencies() {
super.didChangeDependencies();
_theme = Theme.of(context);
}
@override
Widget build(BuildContext context) {
_value = 'Tap me to crash';
return FilledButton.tonal(onPressed: _showPopup, child: const Text('Press me'));
}
void _showPopup() async {
if (_openInProgress) return;
_openInProgress = true;
_popup ??= OverlayEntry(builder: _popupBuilder);
Overlay.of(context).insert(_popup!);
await _animationController.forward(from: 0);
setState(() {});
}
Widget _popupBuilder(BuildContext context) {
return PositionedDirectional(
bottom: 0,
start: 0,
end: 0,
child: SizedBox(
height: 322.0,
child: Material(
color: _theme.colorScheme.secondaryContainer,
type: MaterialType.card,
child: ThePopUp(initialValue: _value),
),
),
);
}
}
typedef PopupOnChanged = void Function(double? amount);
class ThePopUp extends StatefulWidget {
const ThePopUp({super.key, required this.initialValue});
final String initialValue;
@override
State<ThePopUp> createState() => _ThePopUpState();
}
class _ThePopUpState extends State<ThePopUp> {
@override
Widget build(BuildContext context) {
return ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 400),
child: ValueDisplay(value: widget.initialValue),
);
}
}
class ValueDisplay extends StatelessWidget {
const ValueDisplay({super.key, required this.value});
final String? value;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return SizedBox(
height: 40,
child: FittedBox(
fit: BoxFit.scaleDown,
child: SelectableText(
value.toString(),
textAlign: TextAlign.center,
maxLines: 1,
style: theme.textTheme.titleLarge,
contextMenuBuilder: _contextMenuBuilder,
),
),
);
}
void _pasteFromClipboard() async {
ContextMenuController.removeAny();
print('Fake paste');
}
Widget _contextMenuBuilder(BuildContext context, EditableTextState state) {
final List<ContextMenuButtonItem> buttonItems = state.contextMenuButtonItems;
final int pasteButtonIndex =
buttonItems.indexWhere((buttonItem) => buttonItem.type == ContextMenuButtonType.paste);
if (pasteButtonIndex >= 0) {
final pasteButton = buttonItems[pasteButtonIndex];
buttonItems[pasteButtonIndex] = pasteButton.copyWith(onPressed: _pasteFromClipboard);
} else {
final pasteButton = ContextMenuButtonItem(
onPressed: _pasteFromClipboard,
type: ContextMenuButtonType.paste,
);
if (buttonItems.length > 1) {
buttonItems[1] = pasteButton;
} else {
buttonItems.add(pasteButton);
}
}
return AdaptiveTextSelectionToolbar.buttonItems(
anchors: state.contextMenuAnchors,
buttonItems: buttonItems,
);
}
}
Steps to reproduce
It happens on both Flutter
3.24.3and3.22.3. Works fine on3.19.6Expected results
Should show the custom context menu instead of crashing
Actual results
Crashes the app
Code sample
Code sample
Screenshots or Video
No response
Logs
Logs
Flutter Doctor output
Doctor output