Skip to content

Using WidgetSpan in TextEditingController for TextField in AlertDialog throws error #136596

@GP4cK

Description

@GP4cK

Is there an existing issue for this?

Steps to reproduce

I built a minimal reproducible sample with a TextEditingController that replaces abc with a WidgetSpan (it surrounds abc with a border)
When the TextField is used inside the main Scaffold, no issue. If it is in an AlertDialog, it crashes.

  1. Clone this repo https://github.com/GP4cK/alert_bug
  2. Tap on the FAB
  3. Type abc in the AlertDialog

Expected results

It should behave the same whether the TextField is in a dialog or not.

Actual results

In the dialog, it throws an exception during layout.

Code sample

Code sample
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Flutter Demo',
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  void openAlert(BuildContext context) {
    showDialog(
        context: context,
        builder: (context) {
          return const AlertDialog(content: MyTextField());
        });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(title),
      ),
      body: const Padding(
        padding: EdgeInsets.all(24.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            MyTextField(),
            SizedBox(height: 24),
            Text('Tap the FAB and type "abc"'),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => openAlert(context),
        child: const Icon(Icons.add),
      ),
    );
  }
}

class MyTextController extends TextEditingController {
  static const textToReplace = 'abc';

  static final replacement = TextSpan(children: [
    WidgetSpan(
      alignment: PlaceholderAlignment.middle,
      baseline: TextBaseline.ideographic,
      child: Container(
        padding: const EdgeInsets.all(1),
        decoration: BoxDecoration(
          borderRadius: const BorderRadius.all(Radius.circular(4)),
          border: Border.all(color: Colors.blue),
        ),
        child: const Text(textToReplace),
      ),
    ),
    // Adds invisible characters to the end of the span to make sure the
    // cursor is at the right place
    // https://stackoverflow.com/questions/66304688/cursor-wrong-position-after-text-replacing-with-textspan.
    TextSpan(text: '\u200b' * (textToReplace.length - 1)),
  ]);

  @override
  TextSpan buildTextSpan(
      {required BuildContext context,
      TextStyle? style,
      required bool withComposing}) {
    return TextSpan(
      children: text
          .split(textToReplace)
          .map((str) =>
              TextSpan(text: str, style: const TextStyle(color: Colors.black)))
          .separated(replacement)
          .toList(),
    );
  }
}

class MyTextField extends StatefulWidget {
  const MyTextField({super.key});

  @override
  State<MyTextField> createState() => _MyTextFieldState();
}

class _MyTextFieldState extends State<MyTextField> {
  final controller = MyTextController();
  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return TextField(
      autofocus: true,
      controller: controller,
    );
  }
}

extension<T> on Iterable<T> {
  /// Puts [separator] between every element.
  ///
  /// Example:
  ///
  /// ```dart
  /// final list1 = <int>[].separated(2); // [];
  /// final list2 = [0].separated(2); // [0];
  /// final list3 = [0, 0].separated(2); // [0, 2, 0];
  /// ```
  Iterable<T> separated(T separator) sync* {
    final iterator = this.iterator;
    if (iterator.moveNext()) {
      yield iterator.current;
      while (iterator.moveNext()) {
        yield separator;
        yield iterator.current;
      }
    }
  }
}

Screenshots or Video

Using the same TextField / TextController, see how there is no problem when I enter 'abc' in the Scaffold of the main app, but it crashes when I enter it in the dialog.

Screenshots / Video demonstration
Video.MP4.584x624.mp4

Logs

Logs
Restarted application in 451ms.

════════ Exception caught by rendering library ═════════════════════════════════
The following assertion was thrown during performLayout():
'package:flutter/src/widgets/widget_span.dart': Failed assertion: line 137 pos 12: 'dimensions != null': is not true.

Either the assertion indicates an error in the framework itself, or we should provide substantially more information in this error message to help you determine and fix the underlying cause.
In either case, please report this assertion by filing a bug on GitHub:
  https://github.com/flutter/flutter/issues/new?template=2_bug.yml

The relevant error-causing widget was
AlertDialog
When the exception was thrown, this was the stack
#2      WidgetSpan.build
#3      TextSpan.build
#4      TextSpan.build
#5      TextPainter._createParagraph
#6      TextPainter.layout
#7      RenderEditable._layoutText
#8      RenderEditable.computeMaxIntrinsicWidth
#9      RenderBox._computeIntrinsicDimension.<anonymous closure>
#10     _LinkedHashMapMixin.putIfAbsent (dart:collection-patch/compact_hash.dart:535:23)
#11     RenderBox._computeIntrinsicDimension
#12     RenderBox.getMaxIntrinsicWidth
#13     RenderProxyBoxMixin.computeMaxIntrinsicWidth
#14     RenderBox._computeIntrinsicDimension.<anonymous closure>
#15     _LinkedHashMapMixin.putIfAbsent (dart:collection-patch/compact_hash.dart:535:23)
#16     RenderBox._computeIntrinsicDimension
#17     RenderBox.getMaxIntrinsicWidth
#18     RenderProxyBoxMixin.computeMaxIntrinsicWidth
#19     RenderBox._computeIntrinsicDimension.<anonymous closure>
#20     _LinkedHashMapMixin.putIfAbsent (dart:collection-patch/compact_hash.dart:535:23)
#21     RenderBox._computeIntrinsicDimension
#22     RenderBox.getMaxIntrinsicWidth
#23     RenderProxyBoxMixin.computeMaxIntrinsicWidth
#24     RenderBox._computeIntrinsicDimension.<anonymous closure>
#25     _LinkedHashMapMixin.putIfAbsent (dart:collection-patch/compact_hash.dart:535:23)
#26     RenderBox._computeIntrinsicDimension
#27     RenderBox.getMaxIntrinsicWidth
#28     RenderProxyBoxMixin.computeMaxIntrinsicWidth
#29     RenderBox._computeIntrinsicDimension.<anonymous closure>
#30     _LinkedHashMapMixin.putIfAbsent (dart:collection-patch/compact_hash.dart:535:23)
#31     RenderBox._computeIntrinsicDimension
#32     RenderBox.getMaxIntrinsicWidth
#33     RenderProxyBoxMixin.computeMaxIntrinsicWidth
#34     RenderBox._computeIntrinsicDimension.<anonymous closure>
#35     _LinkedHashMapMixin.putIfAbsent (dart:collection-patch/compact_hash.dart:535:23)
#36     RenderBox._computeIntrinsicDimension
#37     RenderBox.getMaxIntrinsicWidth
#38     RenderProxyBoxMixin.computeMaxIntrinsicWidth
#39     RenderBox._computeIntrinsicDimension.<anonymous closure>
#40     _LinkedHashMapMixin.putIfAbsent (dart:collection-patch/compact_hash.dart:535:23)
#41     RenderBox._computeIntrinsicDimension
#42     RenderBox.getMaxIntrinsicWidth
#43     RenderProxyBoxMixin.computeMaxIntrinsicWidth
#44     RenderBox._computeIntrinsicDimension.<anonymous closure>
#45     _LinkedHashMapMixin.putIfAbsent (dart:collection-patch/compact_hash.dart:535:23)
#46     RenderBox._computeIntrinsicDimension
#47     RenderBox.getMaxIntrinsicWidth
#48     RenderProxyBoxMixin.computeMaxIntrinsicWidth
#49     RenderBox._computeIntrinsicDimension.<anonymous closure>
#50     _LinkedHashMapMixin.putIfAbsent (dart:collection-patch/compact_hash.dart:535:23)
#51     RenderBox._computeIntrinsicDimension
#52     RenderBox.getMaxIntrinsicWidth
#53     RenderProxyBoxMixin.computeMaxIntrinsicWidth
#54     RenderBox._computeIntrinsicDimension.<anonymous closure>
#55     _LinkedHashMapMixin.putIfAbsent (dart:collection-patch/compact_hash.dart:535:23)
#56     RenderBox._computeIntrinsicDimension
#57     RenderBox.getMaxIntrinsicWidth
#58     RenderProxyBoxMixin.computeMaxIntrinsicWidth
#59     RenderBox._computeIntrinsicDimension.<anonymous closure>
#60     _LinkedHashMapMixin.putIfAbsent (dart:collection-patch/compact_hash.dart:535:23)
#61     RenderBox._computeIntrinsicDimension
#62     RenderBox.getMaxIntrinsicWidth
#63     _RenderDecoration._maxWidth
#64     _RenderDecoration.computeMaxIntrinsicWidth
#65     RenderBox._computeIntrinsicDimension.<anonymous closure>
#66     _LinkedHashMapMixin.putIfAbsent (dart:collection-patch/compact_hash.dart:535:23)
#67     RenderBox._computeIntrinsicDimension
#68     RenderBox.getMaxIntrinsicWidth
#69     RenderProxyBoxMixin.computeMaxIntrinsicWidth
#70     RenderBox._computeIntrinsicDimension.<anonymous closure>
#71     _LinkedHashMapMixin.putIfAbsent (dart:collection-patch/compact_hash.dart:535:23)
#72     RenderBox._computeIntrinsicDimension
#73     RenderBox.getMaxIntrinsicWidth
#74     RenderProxyBoxMixin.computeMaxIntrinsicWidth
#75     RenderBox._computeIntrinsicDimension.<anonymous closure>
#76     _LinkedHashMapMixin.putIfAbsent (dart:collection-patch/compact_hash.dart:535:23)
#77     RenderBox._computeIntrinsicDimension
#78     RenderBox.getMaxIntrinsicWidth
#79     RenderProxyBoxMixin.computeMaxIntrinsicWidth
#80     RenderBox._computeIntrinsicDimension.<anonymous closure>
#81     _LinkedHashMapMixin.putIfAbsent (dart:collection-patch/compact_hash.dart:535:23)
#82     RenderBox._computeIntrinsicDimension
#83     RenderBox.getMaxIntrinsicWidth
#84     RenderProxyBoxMixin.computeMaxIntrinsicWidth
#85     RenderBox._computeIntrinsicDimension.<anonymous closure>
#86     _LinkedHashMapMixin.putIfAbsent (dart:collection-patch/compact_hash.dart:535:23)
#87     RenderBox._computeIntrinsicDimension
#88     RenderBox.getMaxIntrinsicWidth
#89     RenderProxyBoxMixin.computeMaxIntrinsicWidth
#90     RenderBox._computeIntrinsicDimension.<anonymous closure>
#91     _LinkedHashMapMixin.putIfAbsent (dart:collection-patch/compact_hash.dart:535:23)
#92     RenderBox._computeIntrinsicDimension
#93     RenderBox.getMaxIntrinsicWidth
#94     RenderProxyBoxMixin.computeMaxIntrinsicWidth
#95     RenderBox._computeIntrinsicDimension.<anonymous closure>
#96     _LinkedHashMapMixin.putIfAbsent (dart:collection-patch/compact_hash.dart:535:23)
#97     RenderBox._computeIntrinsicDimension
#98     RenderBox.getMaxIntrinsicWidth
#99     RenderPadding.computeMaxIntrinsicWidth
#100    RenderBox._computeIntrinsicDimension.<anonymous closure>
#101    _LinkedHashMapMixin.putIfAbsent (dart:collection-patch/compact_hash.dart:535:23)
#102    RenderBox._computeIntrinsicDimension
#103    RenderBox.getMaxIntrinsicWidth
#104    RenderFlex.computeMaxIntrinsicWidth.<anonymous closure>
#105    RenderFlex._getIntrinsicSize
#106    RenderFlex.computeMaxIntrinsicWidth
#107    RenderBox._computeIntrinsicDimension.<anonymous closure>
#108    _LinkedHashMapMixin.putIfAbsent (dart:collection-patch/compact_hash.dart:535:23)
#109    RenderBox._computeIntrinsicDimension
#110    RenderBox.getMaxIntrinsicWidth
#111    RenderIntrinsicWidth._computeSize
#112    RenderIntrinsicWidth.performLayout
#113    RenderObject.layout
#114    RenderBox.layout
#115    RenderProxyBoxMixin.performLayout
#116    RenderObject.layout
#117    RenderBox.layout
#118    RenderProxyBoxMixin.performLayout
#119    RenderCustomPaint.performLayout
#120    RenderObject.layout
#121    RenderBox.layout
#122    RenderProxyBoxMixin.performLayout
#123    _RenderCustomClip.performLayout
#124    RenderObject.layout
#125    RenderBox.layout
#126    RenderConstrainedBox.performLayout
#127    RenderObject.layout
#128    RenderBox.layout
#129    RenderPositionedBox.performLayout
#130    RenderObject._layoutWithoutResize
#131    PipelineOwner.flushLayout
#132    RendererBinding.drawFrame
#133    WidgetsBinding.drawFrame
#134    RendererBinding._handlePersistentFrameCallback
#135    SchedulerBinding._invokeFrameCallback
#136    SchedulerBinding.handleDrawFrame
#137    SchedulerBinding._handleDrawFrame
#138    _invoke (dart:ui/hooks.dart:170:13)
#139    PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:401:5)
#140    _drawFrame (dart:ui/hooks.dart:140:31)
(elided 2 frames from class _AssertionError)
The following RenderObject was being processed when the exception was fired: RenderIntrinsicWidth#18c0a relayoutBoundary=up5 NEEDS-LAYOUT
RenderObject: RenderIntrinsicWidth#18c0a relayoutBoundary=up5 NEEDS-LAYOUT
    needs compositing
    parentData: <none> (can use size)
    constraints: BoxConstraints(280.0<=w<=720.0, 0.0<=h<=552.0)
    size: Size(280.0, 92.0)
    stepWidth: null
    stepHeight: null
    child: RenderFlex#1f5bb relayoutBoundary=up6 NEEDS-LAYOUT
        needs compositing
        parentData: <none> (can use size)
        constraints: BoxConstraints(w=280.0, 0.0<=h<=552.0)
        size: Size(280.0, 92.0)
        direction: vertical
        mainAxisAlignment: start
        mainAxisSize: min
        crossAxisAlignment: stretch
        verticalDirection: down
        child 1: RenderPadding#2aba3 relayoutBoundary=up7 NEEDS-LAYOUT
            needs compositing
            parentData: offset=Offset(0.0, 0.0); flex=1; fit=FlexFit.loose (can use size)
            constraints: BoxConstraints(w=280.0, 0.0<=h<=552.0)
            size: Size(280.0, 92.0)
            padding: EdgeInsets(24.0, 20.0, 24.0, 24.0)
            textDirection: ltr
            child: RenderSemanticsAnnotations#5b481 relayoutBoundary=up8 NEEDS-LAYOUT
                needs compositing
                parentData: offset=Offset(24.0, 20.0) (can use size)
                constraints: BoxConstraints(w=232.0, 0.0<=h<=508.0)
                semantic boundary
                size: Size(232.0, 48.0)
                child: RenderMouseRegion#b755f relayoutBoundary=up9 NEEDS-LAYOUT
                    needs compositing
                    parentData: <none> (can use size)
                    constraints: BoxConstraints(w=232.0, 0.0<=h<=508.0)
                    size: Size(232.0, 48.0)
                    behavior: opaque
                    listeners: enter, exit
                    cursor: SystemMouseCursor(text)
════════════════════════════════════════════════════════════════════════════════

════════ Exception caught by rendering library ═════════════════════════════════
'package:flutter/src/widgets/widget_span.dart': Failed assertion: line 137 pos 12: 'dimensions != null': is not true.
════════════════════════════════════════════════════════════════════════════════

════════ Exception caught by rendering library ═════════════════════════════════
'package:flutter/src/widgets/widget_span.dart': Failed assertion: line 137 pos 12: 'dimensions != null': is not true.
════════════════════════════════════════════════════════════════════════════════

════════ Exception caught by scheduler library ═════════════════════════════════
'package:flutter/src/widgets/widget_span.dart': Failed assertion: line 137 pos 12: 'dimensions != null': is not true.
════════════════════════════════════════════════════════════════════════════════

════════ Exception caught by scheduler library ═════════════════════════════════
'package:flutter/src/widgets/widget_span.dart': Failed assertion: line 137 pos 12: 'dimensions != null': is not true.
════════════════════════════════════════════════════════════════════════════════

Flutter Doctor output

Doctor output
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.13.7, on macOS 13.4.1 22F770820d darwin-x64, locale en-GB)
[!] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
    ✗ cmdline-tools component is missing
      Run `path/to/sdkmanager --install "cmdline-tools;latest"`
      See https://developer.android.com/studio/command-line for more details.
    ✗ Android license status unknown.
      Run `flutter doctor --android-licenses` to accept the SDK licenses.
      See https://flutter.dev/docs/get-started/install/macos#android-setup for more details.
[✓] Xcode - develop for iOS and macOS (Xcode 14.3.1)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2022.3)
[✓] VS Code (version 1.83.1)
[✓] Connected device (2 available)
[✓] Network resources

! Doctor found issues in 1 category.

Metadata

Metadata

Labels

a: error messageError messages from the Flutter frameworka: text inputEntering text in a text field or keyboard related problemsf: material designflutter/packages/flutter/material repository.found in release: 3.13Found to occur in 3.13found in release: 3.16Found to occur in 3.16has reproducible stepsThe issue has been confirmed reproducible and is ready to work onteam-designOwned by Design Languages team

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions