Instances of `RenderEditable` must be laid out before processing hit testing. Trying to hit-test a `RenderEditable` object before layout results in an assertion such as the following:
Original behavior
To support gesture recognizers in selectable text, the `RenderEditable` requires the layout information for its text spans to determine which text span receives the pointer event. (Before this change, `RenderEditable` objects didn't take their text into account when evaluating hit tests.) To implement this, layout was made a prerequisite for performing hit testing on a `RenderEditable` object.
New behavior
Not stated in the source.
- Who is affected
- Applications and packages relying on the previous behaviour or signature. Code that uses RenderEditable.
- Practical impact
- Behaviour or API differences that can break builds or change runtime behaviour.
- What action should I take?
- Follow the official migration guide below, update affected call sites, run `dart fix --apply` where a fix is available, then run your tests.
Before and after (from the official guide)
| 1 | import 'package:flutter/rendering.dart'; | |
| 2 | import 'package:flutter_test/flutter_test.dart'; | |
| 3 | import 'package:material_ui/material_ui.dart'; | |
| 4 | ||
| 5 | void main() { | |
| 6 | test('attach and detach correctly handle gesture', () { | |
| 7 | final RenderEditable editable = RenderEditable( | |
| 8 | textDirection: TextDirection.ltr, | |
| 9 | offset: ViewportOffset.zero(), | |
| 10 | textSelectionDelegate: FakeEditableTextState(), | |
| 11 | startHandleLayerLink: LayerLink(), | |
| 12 | endHandleLayerLink: LayerLink(), | |
| 13 | ); | |
| 14 | final PipelineOwner owner = PipelineOwner(onNeedVisualUpdate: () {}); | |
| 15 | editable.attach(owner); | |
| 16 | − | // This throws an assertion error because |
| 17 | − | // the RenderEditable hasn't been laid out. |
| 18 | editable.handleEvent(const PointerDownEvent(), | |
| 19 | BoxHitTestEntry(editable, const Offset(10, 10))); | |
| 20 | editable.detach(); | |
| 21 | }); | |
| 22 | } | |
| 23 | ||
| 24 | class FakeEditableTextState extends TextSelectionDelegate { | |
| 25 | @override | |
| 26 | TextEditingValue textEditingValue; | |
| 27 | @override | |
| 28 | void hideToolbar() {} | |
| 29 | @override | |
| 30 | void bringIntoView(TextPosition position) {} | |
| 31 | } |
| 1 | import 'package:flutter/rendering.dart'; | |
| 2 | import 'package:flutter_test/flutter_test.dart'; | |
| 3 | import 'package:material_ui/material_ui.dart'; | |
| 4 | ||
| 5 | void main() { | |
| 6 | test('attach and detach correctly handle gesture', () { | |
| 7 | final RenderEditable editable = RenderEditable( | |
| 8 | textDirection: TextDirection.ltr, | |
| 9 | offset: ViewportOffset.zero(), | |
| 10 | textSelectionDelegate: FakeEditableTextState(), | |
| 11 | startHandleLayerLink: LayerLink(), | |
| 12 | endHandleLayerLink: LayerLink(), | |
| 13 | ); | |
| 14 | + | // Lay out the RenderEditable first. |
| 15 | + | editable.layout(BoxConstraints.loose(const Size(1000.0, 1000.0))); |
| 16 | final PipelineOwner owner = PipelineOwner(onNeedVisualUpdate: () {}); | |
| 17 | editable.attach(owner); | |
| 18 | editable.handleEvent(const PointerDownEvent(), | |
| 19 | BoxHitTestEntry(editable, const Offset(10, 10))); | |
| 20 | editable.detach(); | |
| 21 | }); | |
| 22 | } | |
| 23 | ||
| 24 | class FakeEditableTextState extends TextSelectionDelegate { | |
| 25 | @override | |
| 26 | TextEditingValue textEditingValue; | |
| 27 | @override | |
| 28 | void hideToolbar() {} | |
| 29 | @override | |
| 30 | void bringIntoView(TextPosition position) {} | |
| 31 | } |
Raw before / after
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:material_ui/material_ui.dart';
void main() {
test('attach and detach correctly handle gesture', () {
final RenderEditable editable = RenderEditable(
textDirection: TextDirection.ltr,
offset: ViewportOffset.zero(),
textSelectionDelegate: FakeEditableTextState(),
startHandleLayerLink: LayerLink(),
endHandleLayerLink: LayerLink(),
);
final PipelineOwner owner = PipelineOwner(onNeedVisualUpdate: () {});
editable.attach(owner);
// This throws an assertion error because
// the RenderEditable hasn't been laid out.
editable.handleEvent(const PointerDownEvent(),
BoxHitTestEntry(editable, const Offset(10, 10)));
editable.detach();
});
}
class FakeEditableTextState extends TextSelectionDelegate {
@override
TextEditingValue textEditingValue;
@override
void hideToolbar() {}
@override
void bringIntoView(TextPosition position) {}
}import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:material_ui/material_ui.dart';
void main() {
test('attach and detach correctly handle gesture', () {
final RenderEditable editable = RenderEditable(
textDirection: TextDirection.ltr,
offset: ViewportOffset.zero(),
textSelectionDelegate: FakeEditableTextState(),
startHandleLayerLink: LayerLink(),
endHandleLayerLink: LayerLink(),
);
// Lay out the RenderEditable first.
editable.layout(BoxConstraints.loose(const Size(1000.0, 1000.0)));
final PipelineOwner owner = PipelineOwner(onNeedVisualUpdate: () {});
editable.attach(owner);
editable.handleEvent(const PointerDownEvent(),
BoxHitTestEntry(editable, const Offset(10, 10)));
editable.detach();
});
}
class FakeEditableTextState extends TextSelectionDelegate {
@override
TextEditingValue textEditingValue;
@override
void hideToolbar() {}
@override
void bringIntoView(TextPosition position) {}
}Official migration guide (excerpt, © The Flutter Authors, CC BY 3.0)
If you see the '!debugNeedsLayout': is not true
assertion error while hit testing the RenderEditable,
lay out the RenderEditable before doing so.
Code before migration:
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:material_ui/material_ui.dart';
void main() {
test('attach and detach correctly handle gesture', () {
final RenderEditable editable = RenderEditable(
textDirection: TextDirection.ltr,
offset: ViewportOffset.zero(),
textSelectionDelegate: FakeEditableTextState(),
startHandleLayerLink: LayerLink(),
endHandleLayerLink: LayerLink(),
);
final PipelineOwner owner = PipelineOwner(onNeedVisualUpdate: () {});
editable.attach(owner);
// This throws an assertion error because
// the RenderEditable hasn't been laid out.
editable.handleEvent(const PointerDownEvent(),
BoxHitTestEntry(editable, const Offset(10, 10)));
editable.detach();
});
}
class FakeEditableTextState extends TextSelectionDelegate {
@override
TextEditingValue textEditingValue;
@override
void hideToolbar() {}
@override
void bringIntoView(TextPosition position) {}
}Code after migration:
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:material_ui/material_ui.dart';
void main() {
test('attach and detach correctly handle gesture', () {
final RenderEditable editable = RenderEditable(
textDirection: TextDirection.ltr,
offset: ViewportOffset.zero(),
textSelectionDelegate: FakeEditableTextState(),
startHandleLayerLink: LayerLink(),
endHandleLayerLink: LayerLink(),
);
// Lay out the RenderEditable first.
editable.layout(BoxConstraints.loose(const Size(1000.0, 1000.0)));
final PipelineOwner owner = PipelineOwner(onNeedVisualUpdate: () {});
editable.attach(owner);
editable.handleEvent(const PointerDownEvent(),
BoxHitTestEntry(editable, const Offset(10, 10)));
editable.detach();
});
}
class FakeEditableTextState extends TextSelectionDelegate {
@override
TextEditingValue textEditingValue;
@override
void hideToolbar() {}
@override
void bringIntoView(TextPosition position) {}
}AI_PROVIDER (for example to openai-compatible with a local Ollama or LM Studio endpoint) to enable grounded explanations. Everything else on this page works without an AI provider.Listed in the official breaking-changes index (docs.flutter.dev/release/breaking-changes).
An official announcement or migration guide documents an intended behaviour or API change. A verified source change (git diff) shows code changed but does not by itself prove a public API is breaking. AI interpretations are labelled and never used to classify a change.
- Official migration guide
- RenderEditable
- Issue 43494
- PR 54479: Enable gesture recognizer in selectable rich text
- Source commit
- b3045439b7c0
- Source last updated
- Oct 21, 2026
- Last synchronized
- Oct 21, 2026
- Publication status
- published
Publication history
- Oct 21, 2026, 8:31 PM: new → published (ingestion) — Backed by an official breaking-change/migration guide.
AI_PROVIDER (for example to openai-compatible with a local Ollama or LM Studio endpoint) to enable grounded explanations. Everything else on this page works without an AI provider.