Skip to content

[macOS] Avoid using TextEditingDeltaNonTextUpdate for syncing state. #118759

@angelosilvestre

Description

@angelosilvestre

On macOS, every time we call setEditingState, the engine sends us back a TextEditingDeltaNonTextUpdate, as we can see in the engine code:

https://github.com/flutter/engine/blob/8ed6790b5977c5d91e99a3c75e5b4ceb17cca38b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm#L349-L364

We have no way to know if the TextEditingDeltaNonTextUpdate is sent because the IME is trying to update the selection, like swiping on the spacebar on Android, or if it's the engine trying to sync its state.

This also happens on Android in some cases, as described in #113253.

Flutter should use a different kind of delta for syncing state, so we can know if we are changing the selection/composing region, or if the engine is syncing its state.

Steps to Reproduce

  1. Execute flutter run on the code sample on macOS
  2. Type some letter

Expected results: We don't get a TextEditingDeltaNonTextUpdate

Actual results: We get a TextEditingDeltaNonTextUpdate

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

void main() {
  runApp(
    MaterialApp(
      home: InputExample(),
    ),
  );
}

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

  @override
  State<InputExample> createState() => _InputExampleState();
}

class _InputExampleState extends State<InputExample> with TextInputClient, DeltaTextInputClient {
  TextInputConnection? _connection;
  TextEditingValue _currentEditingValue = const TextEditingValue();
  TextEditingValue _lastValueSent = const TextEditingValue();

  @override
  void initState() {
    super.initState();

    _attachToIme();
  }

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

  @override
  void updateEditingValueWithDeltas(List<TextEditingDelta> textEditingDeltas) {
    TextEditingValue newEditingValue = _currentEditingValue;

    for (final delta in textEditingDeltas) {
      print('Processing delta: ${delta}');
      // We could make arbitrary changes to the newEditingValue
      newEditingValue = delta.apply(newEditingValue);
    }

    _updateEditingStateOnPlatform(newEditingValue);

    setState(() {
      _currentEditingValue = newEditingValue;
    });
  }

  void _attachToIme() {
    const config = TextInputConfiguration(
      enableDeltaModel: true,
    );
    _connection = TextInput.attach(this, config);
    _connection!.show();
    print('attached to IME');
  }

  void _detachFromIme() {
    _connection?.close();
    print('detached from IME');
  }

  void _updateEditingStateOnPlatform(TextEditingValue value) {
    if (_connection == null) {
      return;
    }
    if (value == _lastValueSent) {
      return;
    }
    _connection!.setEditingState(value);
    _lastValueSent = value;
  }

  @override
  void connectionClosed() {}

  @override
  AutofillScope? get currentAutofillScope => throw UnimplementedError();

  @override
  TextEditingValue? get currentTextEditingValue => throw UnimplementedError();

  @override
  void performAction(TextInputAction action) {}

  @override
  void performPrivateCommand(String action, Map<String, dynamic> data) {}

  @override
  void showAutocorrectionPromptRect(int start, int end) {}

  @override
  void updateEditingValue(TextEditingValue value) {}

  @override
  void updateFloatingCursor(RawFloatingCursorPoint point) {}

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Text(_currentEditingValue.text),
      ),
    );
  }
}
Logs
flutter run --verbose

No relevant info
flutter analyze

No relevant info
flutter doctor -v

[!] Flutter (Channel master, 3.7.0-23.0.pre.16, on macOS 12.6.1 21G217
    darwin-x64, locale pt-BR)
    • Flutter version 3.7.0-23.0.pre.16 on channel master at
      /Users/user223563/Downloads/flutter
    ! The flutter binary is not on your path. Consider adding
      /Users/user223563/Downloads/flutter/bin to your path.
    ! The dart binary is not on your path. Consider adding
      /Users/user223563/Downloads/flutter/bin to your path.
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision b3059d2c09 (41 minutes ago), 2023-01-18 23:27:27 +0000
    • Engine revision 290636c1cb
    • Dart version 3.0.0 (build 3.0.0-122.0.dev)
    • DevTools version 2.20.0
    • If those were intentional, you can disregard the above warnings; however
      it is recommended to use "git" directly to perform update checks and
      upgrades.

[✗] Android toolchain - develop for Android devices
    ✗ Unable to locate Android SDK.
      Install Android Studio from:
      https://developer.android.com/studio/index.html
      On first launch it will assist you in installing the Android SDK
      components.
      (or visit https://flutter.dev/docs/get-started/install/macos#android-setup
      for detailed instructions).
      If the Android SDK has been installed to a custom location, please use
      `flutter config --android-sdk` to update to that location.


[✓] Xcode - develop for iOS and macOS (Xcode 14.2)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 14C18
    • CocoaPods version 1.11.3

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 2021.3)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build
      11.0.13+0-b1751.21-8125866)

[✓] IntelliJ IDEA Community Edition (version 2020.3.3)
    • IntelliJ at /Applications/IntelliJ IDEA CE.app
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart

[✓] VS Code (version 1.70.2)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.56.0

[✓] Connected device (2 available)
    • macOS (desktop) • macos  • darwin-x64     • macOS 12.6.1 21G217 darwin-x64
    • Chrome (web)    • chrome • web-javascript • Google Chrome 109.0.5414.87

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

! Doctor found issues in 2 categories.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Issues that are less important to the Flutter projecta: desktopRunning on desktopa: text inputEntering text in a text field or keyboard related problemsengineflutter/engine related. See also e: labels.found in release: 3.3Found to occur in 3.3found in release: 3.7Found to occur in 3.7has reproducible stepsThe issue has been confirmed reproducible and is ready to work onplatform-macBuilding on or for macOS specificallyr: fixedIssue is closed as already fixed in a newer version

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions