Repository navigation
SelectableText crashes the app if custom contextMenuBuilder is used, Null check operator used on a null value #155514
Copy link
Copy link
Closed
Labels
P2Important issues not at the top of the work listImportant issues not at the top of the work lista: error messageError messages from the Flutter frameworkError messages from the Flutter frameworkc: regressionIt was better in the past than it is nowIt was better in the past than it is nowf: selectionSelectableRegion, SelectionArea, SelectionContainer, Selectable, and related APIsSelectableRegion, SelectionArea, SelectionContainer, Selectable, and related APIsfound in release: 3.24Found to occur in 3.24Found to occur in 3.24found in release: 3.26Found to occur in 3.26Found to occur in 3.26frameworkflutter/packages/flutter repository. See also f: labels.flutter/packages/flutter repository. See also f: labels.has reproducible stepsThe issue has been confirmed reproducible and is ready to work onThe issue has been confirmed reproducible and is ready to work onteam-text-inputOwned by Text Input teamOwned by Text Input teamtriaged-text-inputTriaged by Text Input teamTriaged by Text Input teamworkaround availableThere is a workaround available to overcome the issueThere is a workaround available to overcome the issue
Description
Steps to reproduce
- Run the code sample
- Press the button, it will open an overlay
- Tap the text, it will crash
It happens on both Flutter 3.24.3 and 3.22.3. Works fine on 3.19.6
Expected results
Should show the custom context menu instead of crashing
Actual results
Crashes the app
Code sample
Code sample
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,
);
}
}
Screenshots or Video
No response
Logs
Logs
======== Exception caught by widgets library =======================================================
The following _TypeError was thrown building Overlay-[LabeledGlobalKey<OverlayState>#de574](state: OverlayState#b0685(entries: [OverlayEntry#809eb(opaque: true; maintainState: false), OverlayEntry#de194(opaque: false; maintainState: true), OverlayEntry#e7ffa(opaque: false; maintainState: false)])):
Null check operator used on a null value
The relevant error-causing widget was:
MaterialApp MaterialApp:file:///issue_reproducibles/lib/flutter/custom_context_menu_builder_issue.dart:15:12
When the exception was thrown, this was the stack:
#0 _OverlayEntryWidgetState.initState (package:flutter/src/widgets/overlay.dart:364:44)
#1 StatefulElement._firstBuild (package:flutter/src/widgets/framework.dart:5748:55)
#2 ComponentElement.mount (package:flutter/src/widgets/framework.dart:5593:5)
#3 Element.inflateWidget (package:flutter/src/widgets/framework.dart:4468:16)
#4 MultiChildRenderObjectElement.inflateWidget (package:flutter/src/widgets/framework.dart:7035:36)
#5 Element.updateChild (package:flutter/src/widgets/framework.dart:3963:18)
#6 Element.updateChildren (package:flutter/src/widgets/framework.dart:4150:32)
#7 MultiChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:7060:17)
#8 Element.updateChild (package:flutter/src/widgets/framework.dart:3941:15)
#9 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5642:16)
#10 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5780:11)
#11 Element.rebuild (package:flutter/src/widgets/framework.dart:5333:7)
#12 BuildScope._tryRebuild (package:flutter/src/widgets/framework.dart:2693:15)
#13 BuildScope._flushDirtyElements (package:flutter/src/widgets/framework.dart:2752:11)
#14 BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:3048:18)
#15 WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:1162:21)
#16 RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:468:5)
#17 SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1397:15)
#18 SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1318:9)
#19 SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:1176:5)
#20 _invoke (dart:ui/hooks.dart:312:13)
#21 PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:419:5)
#22 _drawFrame (dart:ui/hooks.dart:283:31)
====================================================================================================
Flutter Doctor output
Doctor output
[√] Flutter (Channel stable, 3.24.3, on Microsoft Windows [Version 10.0.22631.4169], locale en-US)
• Flutter version 3.24.3 on channel stable at D:\Apps\flutter
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision 2663184aa7 (11 days ago), 2024-09-11 16:27:48 -0500
• Engine revision 36335019a8
• Dart version 3.5.3
• DevTools version 2.37.3Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
P2Important issues not at the top of the work listImportant issues not at the top of the work lista: error messageError messages from the Flutter frameworkError messages from the Flutter frameworkc: regressionIt was better in the past than it is nowIt was better in the past than it is nowf: selectionSelectableRegion, SelectionArea, SelectionContainer, Selectable, and related APIsSelectableRegion, SelectionArea, SelectionContainer, Selectable, and related APIsfound in release: 3.24Found to occur in 3.24Found to occur in 3.24found in release: 3.26Found to occur in 3.26Found to occur in 3.26frameworkflutter/packages/flutter repository. See also f: labels.flutter/packages/flutter repository. See also f: labels.has reproducible stepsThe issue has been confirmed reproducible and is ready to work onThe issue has been confirmed reproducible and is ready to work onteam-text-inputOwned by Text Input teamOwned by Text Input teamtriaged-text-inputTriaged by Text Input teamTriaged by Text Input teamworkaround availableThere is a workaround available to overcome the issueThere is a workaround available to overcome the issue
Activity
danagbemava-nc commentedon Sep 23, 2024
Reproducible using the code sample and steps provided above. I can confirm that the error message does appear in 3.22.3+ but not in 3.19.6
flutter doctor -v
TahaTesser commentedon Sep 24, 2024
This is caused by #142463 cc: @justinmc
Here i created more simpler sample. This only reproduces in an
Overlay.Code sample
expand to view the code sample
justinmc commentedon Sep 27, 2024
Thank you for the analysis @TahaTesser! It's reassuring that this only happens in an Overlay at least. I want to look at this but likely won't get a chance for a week or two. I will assign myself if I'm working on it.
justinmc commentedon Oct 16, 2024
You can workaround this by defining your custom contextMenuBuilder in a separate method rather than an inline lambda. That ensures that it isn't redefined on each build and it passes an equality check with itself. Here is @TahaTesser's code from above modified with the workaround:
Example code with workaround
Diff
justinmc commentedon Oct 16, 2024
I think the root cause here is the fact that TextSelectionOverlay was never meant to have its values updated, and so we simply dispose and recreate it whenever they do:
flutter/packages/flutter/lib/src/widgets/editable_text.dart
Lines 3075 to 3076 in b397ee3
In this case, the OverlayEntries are built and then immediately disposed, and when initState is called on their _OverlayEntryWidgetStates, their OverlayEntry has already been disposed, causing the error
Null check operator used on a null value.Really, we should come up with a way to update these values that doesn't involved disposing and recreating the overlay.
CC @LongCatIsLooong in case you happen to have any ideas or think that there is something to fix in Overlay. Otherwise I think everyone should use the workaround in the meantime until we can do a bigger refactor of TextSelectionOverlay.
Fix SelectableText crash with inline lambda contextMenuBuilder (flutt…
github-actions commentedon May 23, 2026
This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of
flutter doctor -vand a minimal reproduction of the issue.