Skip to content

Text input is slow if text is very long or there are many TextSpans #114158

@kaboc

Description

@kaboc

Details

I have my own package with a custom TextEditingController that decorates strings. With the package, text input is very slow if the text has thousands of lines. I had suspected that parsing text or building TextSpans was expensive, but I noticed TextField itself without the package was slow with long text.

App developers who want to create an app with text editing have no option but to use another framework if Flutter can handle only short text.

Example 1

  1. Copy the code below and paste it into DartPad.
  2. Press the "Run" button.
  3. Copy very long text and paste it into the TextField in the App.
  4. Type some letters quickly.
  5. Each typed letter appears in the TextField slowly.
Reproducible code
import 'package:flutter/material.dart';

void main() => runApp(const App());

class App extends StatelessWidget {
  const App();

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: Scaffold(
        body: SafeArea(
          child: SizedBox.expand(
            child: TextField(maxLines: null),
          ),
        ),
      ),
    );
  }
}

Example 2

I also tried a TextEditingController with a large number of TextSpans. In the example app below, buildTextSpan() of TextEditingController returns one of two different TextSpans in turn every time the cursor position changes. Each TextSpan has 10,000 lines with 5 children per line.

  1. Run flutter create bug.
  2. Replace the content of main.dart in the created project with the code below.
  3. Run flutter run.
  4. Move the cursor in the TextField quickly with arrow keys.
  5. The text switches with delay.

It does not look like the debug and profile modes have significant difference in the responsiveness.

Reproducible code
import 'package:flutter/material.dart';

void main() => runApp(const App());

class App extends StatefulWidget {
  const App();

  @override
  State<App> createState() => _AppState();
}

class _AppState extends State<App> {
  final _controller = MyTextEditingController();

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: SafeArea(
          child: SizedBox.expand(
            child: TextField(
              controller: _controller,
              maxLines: null,
              autofocus: true,
            ),
          ),
        ),
      ),
    );
  }
}

class MyTextEditingController extends TextEditingController {
  MyTextEditingController() {
    _prepareTextSpans();
  }

  final List<TextSpan> _textSpans1 = [];
  final List<TextSpan> _textSpans2 = [];
  String _text = '';
  TextSelection? _prevSelection;
  bool _flag = false;

  @override
  TextSpan buildTextSpan({
    required BuildContext context,
    TextStyle? style,
    required bool withComposing,
  }) {
    if (selection != _prevSelection) {
      _flag = !_flag;
    }
    _prevSelection = selection;

    WidgetsBinding.instance.addPostFrameCallback((_) {
      value = value.copyWith(text: _text);
    });

    return TextSpan(
      style: style,
      children: _flag ? _textSpans1 : _textSpans2,
    );
  }

  void _prepareTextSpans() {
    for (var i = 0; i < 10000; i++) {
      for (var j = 0; j < 4; j++) {
        _textSpans1.add(
          TextSpan(
            text: j.isEven ? 'aaaaa' : 'bbbbb',
            style: j.isEven ? const TextStyle(color: Colors.orange) : null,
          ),
        );
        _textSpans2.add(
          TextSpan(
            text: j.isEven ? 'aaaaa' : 'bbbbb',
            style: j.isEven ? null : const TextStyle(color: Colors.orange),
          ),
        );
      }
      _textSpans1.add(const TextSpan(text: '\n'));
      _textSpans2.add(const TextSpan(text: '\n'));
    }
    _text = _textSpans1.map((v) => v.text).join();
  }
}

Logs

Logs
[√] Flutter (Channel stable, 3.3.5, on Microsoft Windows [Version 10.0.22000.1098], locale ja-JP)
    • Flutter version 3.3.5 on channel stable at D:\flutter\sdk
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision d9111f6402 (8 days ago), 2022-10-19 12:27:13 -0700
    • Engine revision 3ad69d7be3
    • Dart version 2.18.2
    • DevTools version 2.15.0

[√] Android toolchain - develop for Android devices (Android SDK version 32.1.0-rc1)
    • Android SDK at D:\android\sdk
    • Platform android-33, build-tools 32.1.0-rc1
    • ANDROID_HOME = D:\android\sdk
    • Java binary at: C:\Program Files\JetBrains\IntelliJ IDEA 2021.3.3\jbr\bin\java
    • Java version OpenJDK Runtime Environment JBR-17.0.4.1+7-469.62-jcef (build 17.0.4.1+7-b469.62)
    • All Android licenses accepted.

[X] Chrome - develop for the web (Cannot find Chrome executable at .\Google\Chrome\Application\chrome.exe)
    ! Cannot find Chrome. Try setting CHROME_EXECUTABLE to a Chrome executable.

[√] Visual Studio - develop for Windows (Visual Studio Community 2022 17.3.1)
    • Visual Studio at C:\Program Files\Microsoft Visual Studio\2022\Community
    • Visual Studio Community 2022 version 17.3.32811.315
    • Windows 10 SDK version 10.0.19041.0

[!] Android Studio (not installed)
    • Android Studio not found; download from https://developer.android.com/studio/index.html
      (or visit https://flutter.dev/docs/get-started/install/windows#android-setup for detailed instructions).

[√] IntelliJ IDEA Ultimate Edition (version 2022.2)
    • IntelliJ at C:\Program Files\JetBrains\IntelliJ IDEA 2021.3.3
    • Flutter plugin version 70.2.5
    • Dart plugin version 222.4345.14

[√] VS Code (version 1.72.2)
    • VS Code at C:\Users\xxxxx\AppData\Local\Programs\Microsoft VS Code
    • Flutter extension version 3.50.0

[√] Connected device (2 available)
    • Windows (desktop) • windows • windows-x64    • Microsoft Windows [Version 10.0.22000.1098]
    • Edge (web)        • edge    • web-javascript • Microsoft Edge 106.0.1370.52

[√] HTTP Host Availability
    • All required HTTP hosts are available

! Doctor found issues in 2 categories.

Metadata

Metadata

Assignees

No one assigned

    Labels

    r: duplicateIssue is closed as a duplicate of an existing issue

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions