Skip to content

TextInputClient in macOS doesn't update the editingValue except for inserting characters  #82124

Description

@osaxma

When creating a custom TextInputClient, macOS does not update the editingValue for the following keystrokes:

  • all arrows
  • shift + an arrow
  • delete
  • cmd+a
  • cmd+c
  • cmd+x
  • cmd+v

The last three were not tested since I'm unable to make a selection with either shift + arrows or cmd+a.

Steps to Reproduce

using the code sample provided below, run the following sequence:

  1. type the sequence abcde
  2. press delete (nothing happen on macOS)
  3. type the sequence efg
  4. press left arrow key till baseOffset = 0 (nothing happen on macOS)
  5. press cmd + a (nothing happen on macOS)
  6. press cmd + c (nothing happen on macOS)
  7. press delete (nothing happen on macOS)

Expected results:
I expected the following result which was produced with the same steps on the web platform.

value => TextEditingValue(text: ┤├, selection: TextSelection(baseOffset: 0, extentOffset: 0, affinity: TextAffinity.downstream, isDirectional: false), composing: TextRange(start: -1, end: -1))
value => TextEditingValue(text: ┤a├, selection: TextSelection(baseOffset: 1, extentOffset: 1, affinity: TextAffinity.downstream, isDirectional: false), composing: TextRange(start: -1, end: -1))
value => TextEditingValue(text: ┤ab├, selection: TextSelection(baseOffset: 2, extentOffset: 2, affinity: TextAffinity.downstream, isDirectional: false), composing: TextRange(start: -1, end: -1))
value => TextEditingValue(text: ┤abc├, selection: TextSelection(baseOffset: 3, extentOffset: 3, affinity: TextAffinity.downstream, isDirectional: false), composing: TextRange(start: -1, end: -1))
value => TextEditingValue(text: ┤abcd├, selection: TextSelection(baseOffset: 4, extentOffset: 4, affinity: TextAffinity.downstream, isDirectional: false), composing: TextRange(start: -1, end: -1))
value => TextEditingValue(text: ┤abcde├, selection: TextSelection(baseOffset: 5, extentOffset: 5, affinity: TextAffinity.downstream, isDirectional: false), composing: TextRange(start: -1, end: -1))
value => TextEditingValue(text: ┤abcd├, selection: TextSelection(baseOffset: 4, extentOffset: 4, affinity: TextAffinity.downstream, isDirectional: false), composing: TextRange(start: -1, end: -1))
value => TextEditingValue(text: ┤abcde├, selection: TextSelection(baseOffset: 5, extentOffset: 5, affinity: TextAffinity.downstream, isDirectional: false), composing: TextRange(start: -1, end: -1))
value => TextEditingValue(text: ┤abcdef├, selection: TextSelection(baseOffset: 6, extentOffset: 6, affinity: TextAffinity.downstream, isDirectional: false), composing: TextRange(start: -1, end: -1))
value => TextEditingValue(text: ┤abcdefg├, selection: TextSelection(baseOffset: 7, extentOffset: 7, affinity: TextAffinity.downstream, isDirectional: false), composing: TextRange(start: -1, end: -1))
value => TextEditingValue(text: ┤abcdefg├, selection: TextSelection(baseOffset: 6, extentOffset: 6, affinity: TextAffinity.downstream, isDirectional: false), composing: TextRange(start: -1, end: -1))
value => TextEditingValue(text: ┤abcdefg├, selection: TextSelection(baseOffset: 5, extentOffset: 5, affinity: TextAffinity.downstream, isDirectional: false), composing: TextRange(start: -1, end: -1))
value => TextEditingValue(text: ┤abcdefg├, selection: TextSelection(baseOffset: 4, extentOffset: 4, affinity: TextAffinity.downstream, isDirectional: false), composing: TextRange(start: -1, end: -1))
value => TextEditingValue(text: ┤abcdefg├, selection: TextSelection(baseOffset: 3, extentOffset: 3, affinity: TextAffinity.downstream, isDirectional: false), composing: TextRange(start: -1, end: -1))
value => TextEditingValue(text: ┤abcdefg├, selection: TextSelection(baseOffset: 2, extentOffset: 2, affinity: TextAffinity.downstream, isDirectional: false), composing: TextRange(start: -1, end: -1))
value => TextEditingValue(text: ┤abcdefg├, selection: TextSelection(baseOffset: 1, extentOffset: 1, affinity: TextAffinity.downstream, isDirectional: false), composing: TextRange(start: -1, end: -1))
value => TextEditingValue(text: ┤abcdefg├, selection: TextSelection(baseOffset: 0, extentOffset: 0, affinity: TextAffinity.downstream, isDirectional: false), composing: TextRange(start: -1, end: -1))
value => TextEditingValue(text: ┤abcdefg├, selection: TextSelection(baseOffset: 0, extentOffset: 7, affinity: TextAffinity.downstream, isDirectional: false), composing: TextRange(start: -1, end: -1))
value => TextEditingValue(text: ┤├, selection: TextSelection(baseOffset: 0, extentOffset: 0, affinity: TextAffinity.downstream, isDirectional: false), composing: TextRange(start: -1, end: -1))
 

Actual results:

flutter: value => TextEditingValue(text: ┤a├, selection: TextSelection(baseOffset: 1, extentOffset: 1, affinity: TextAffinity.upstream, isDirectional: false), composing: TextRange(start: -1, end: -1))
flutter: value => TextEditingValue(text: ┤ab├, selection: TextSelection(baseOffset: 2, extentOffset: 2, affinity: TextAffinity.upstream, isDirectional: false), composing: TextRange(start: -1, end: -1))
flutter: value => TextEditingValue(text: ┤abc├, selection: TextSelection(baseOffset: 3, extentOffset: 3, affinity: TextAffinity.upstream, isDirectional: false), composing: TextRange(start: -1, end: -1))
flutter: value => TextEditingValue(text: ┤abcd├, selection: TextSelection(baseOffset: 4, extentOffset: 4, affinity: TextAffinity.upstream, isDirectional: false), composing: TextRange(start: -1, end: -1))
flutter: value => TextEditingValue(text: ┤abcde├, selection: TextSelection(baseOffset: 5, extentOffset: 5, affinity: TextAffinity.upstream, isDirectional: false), composing: TextRange(start: -1, end: -1))
flutter: value => TextEditingValue(text: ┤abcdee├, selection: TextSelection(baseOffset: 6, extentOffset: 6, affinity: TextAffinity.upstream, isDirectional: false), composing: TextRange(start: -1, end: -1))
flutter: value => TextEditingValue(text: ┤abcdeef├, selection: TextSelection(baseOffset: 7, extentOffset: 7, affinity: TextAffinity.upstream, isDirectional: false), composing: TextRange(start: -1, end: -1))
flutter: value => TextEditingValue(text: ┤abcdeefg├, selection: TextSelection(baseOffset: 8, extentOffset: 8, affinity: TextAffinity.upstream, isDirectional: false), composing: TextRange(start: -1, end: -1))
Minimal Code Sample
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  FocusNode focusNode;
  TextInputConnection textInputConnection;
  @override
  void initState() {
    super.initState();
    textInputConnection = TextInput.attach(
      CustomTextInputClient(),
      TextInputConfiguration(
          inputType: TextInputType.text,
          inputAction: TextInputAction.newline,
      ),
    );

    textInputConnection.show();
  }

  @override
  void dispose() {
    textInputConnection.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Container(),
    );
  }
}

class CustomTextInputClient extends TextInputClient {
  @override
  void connectionClosed() {
    // TODO: implement connectionClosed
  }

  @override
  // TODO: implement currentAutofillScope
  AutofillScope get currentAutofillScope {
    throw UnimplementedError();
  }

  @override
  // TODO: implement currentTextEditingValue
  TextEditingValue get currentTextEditingValue {
    throw UnimplementedError();
  }

  @override
  void performAction(TextInputAction action) {
    // TODO: implement performAction
  }

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

  @override
  void showAutocorrectionPromptRect(int start, int end) {
    // TODO: implement showAutocorrectionPromptRect
  }

  @override
  void updateEditingValue(TextEditingValue value) {
    // TODO: implement updateEditingValue
    print('value => $value');
  }

  @override
  void updateFloatingCursor(RawFloatingCursorPoint point) {
    // TODO: implement updateFloatingCursor
  }
}
flutter doctor -v
[✓] Flutter (Channel stable, 2.0.6, on macOS 11.2.3 20D91 darwin-x64, locale en)
    • Flutter version 2.0.6 at /Users/osaxma/fvm/versions/stable
    • Framework revision 1d9032c7e1 (9 days ago), 2021-04-29 17:37:58 -0700
    • Engine revision 05e680e202
    • Dart version 2.12.3

[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
    • Android SDK at /Users/osaxma/Library/Android/sdk
    • Platform android-30, build-tools 29.0.2
    • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6915495)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Xcode 12.4, Build version 12D4e
    • CocoaPods version 1.10.1

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

[✓] Android Studio (version 4.1)
    • 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 1.8.0_242-release-1644-b3-6915495)

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

[✓] Connected device (2 available)
    • macOS (desktop) • macos  • darwin-x64     • macOS 11.2.3 20D91 darwin-x64
    • Chrome (web)    • chrome • web-javascript • Google Chrome 90.0.4430.93

• No issues found!

side note:

I'm not sure if I'm missing something out since TextField works fine. I tried to look at the EditableText implementation to see if there's anything unique about macOS or desktop but I couldn't find such indication. Nevertheless, given that the same provided sample code works for web, I was expecting the same behavior on macOS. Please advise.

Activity

  1. darshankawar commented on May 10, 2021

    @darshankawar
    Member

    Thanks for the detailed report and code sample. I see the same behavior.

    An Observatory debugger and profiler on macOS is available at: http://127.0.0.1:53476/x2YvtWbVytw=/
    The Flutter DevTools debugger and profiler on macOS is available at: http://127.0.0.1:9100?uri=http%3A%2F%2F127.0.0.1%3A53476%2Fx2YvtWbVytw%3D%2F
    flutter: value => TextEditingValue(text: ┤a├, selection: TextSelection(baseOffset: 1, extentOffset: 1, affinity: TextAffinity.upstream, isDirectional: false), composing: TextRange(start: -1, end: -1))
    flutter: value => TextEditingValue(text: ┤ab├, selection: TextSelection(baseOffset: 2, extentOffset: 2, affinity: TextAffinity.upstream, isDirectional: false), composing: TextRange(start: -1, end: -1))
    flutter: value => TextEditingValue(text: ┤abc├, selection: TextSelection(baseOffset: 3, extentOffset: 3, affinity: TextAffinity.upstream, isDirectional: false), composing: TextRange(start: -1, end: -1))
    flutter: value => TextEditingValue(text: ┤abcd├, selection: TextSelection(baseOffset: 4, extentOffset: 4, affinity: TextAffinity.upstream, isDirectional: false), composing: TextRange(start: -1, end: -1))
    flutter: value => TextEditingValue(text: ┤abcde├, selection: TextSelection(baseOffset: 5, extentOffset: 5, affinity: TextAffinity.upstream, isDirectional: false), composing: TextRange(start: -1, end: -1))
    flutter: value => TextEditingValue(text: ┤abcdee├, selection: TextSelection(baseOffset: 6, extentOffset: 6, affinity: TextAffinity.upstream, isDirectional: false), composing: TextRange(start: -1, end: -1))
    flutter: value => TextEditingValue(text: ┤abcdeef├, selection: TextSelection(baseOffset: 7, extentOffset: 7, affinity: TextAffinity.upstream, isDirectional: false), composing: TextRange(start: -1, end: -1))
    flutter: value => TextEditingValue(text: ┤abcdeefg├, selection: TextSelection(baseOffset: 8, extentOffset: 8, affinity: TextAffinity.upstream, isDirectional: false), composing: TextRange(start: -1, end: -1))
    
    
    
    flutter doctor -v
    
    [✓] Flutter (Channel master, 2.3.0-1.0.pre.269, on Mac OS X 10.15.4 19E2269
        darwin-x64, locale en-GB)
        • Flutter version 2.3.0-1.0.pre.269 at
          /Users/dhs/documents/fluttersdk/flutter
        • Upstream repository https://github.com/flutter/flutter.git
        • Framework revision d28737eedf (10 hours ago), 2021-05-09 14:24:19 -0700
        • Engine revision f5b97d0b23
        • Dart version 2.14.0 (build 2.14.0-74.0.dev)
    
    [!] Xcode - develop for iOS and macOS
        • Xcode at /Applications/Xcode.app/Contents/Developer
        • Xcode 12.3, Build version 12C33
        ! CocoaPods 1.9.3 out of date (1.10.0 is recommended).
            CocoaPods is used to retrieve the iOS and macOS platform side's plugin
            code that responds to your plugin usage on the Dart side.
            Without CocoaPods, plugins will not work on iOS or macOS.
            For more info, see https://flutter.dev/platform-plugins
          To upgrade see
          https://guides.cocoapods.org/using/getting-started.html#installation for
          instructions.
    
    [✓] Chrome - develop for the web
        • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
    
    [✓] VS Code (version 1.55.2)
        • VS Code at /Applications/Visual Studio Code.app/Contents
        • Flutter extension version 3.21.0
    
    [✓] Connected device (3 available)
        • iPhone 12 Pro Max (mobile) • A5473606-0213-4FD8-BA16-553433949729 • ios
          • com.apple.CoreSimulator.SimRuntime.iOS-14-3 (simulator)
        • macOS (desktop)            • macos                                •
          darwin-x64     • Mac OS X 10.15.4 19E2269 darwin-x64
        • Chrome (web)               • chrome                               •
          web-javascript • Google Chrome 90.0.4430.93
    
    ! Doctor found issues in 1 category.
    
    
  2. added
    a: text inputEntering text in a text field or keyboard related problems
    a: typographyText rendering, possibly libtxt
    engineflutter/engine related. See also e: labels.
    has reproducible stepsThe issue has been confirmed reproducible and is ready to work on
    and removed
    in triagePresently being triaged by the triage team
    on May 10, 2021
  3. gspencergoog commented on May 13, 2021

    @gspencergoog
    Contributor

    We have changed the text input so that the text editing operations are handled by the shortcuts layer instead of the engine, so perhaps there is something special about macOS in that new code that needs addressing here (that TextField is addressing).

  4. added
    P2Important issues not at the top of the work list
    on May 13, 2021
  5. gspencergoog commented on May 13, 2021

    @gspencergoog
    Contributor
  6. justinmc commented on Jun 23, 2021

    @justinmc
    Contributor

    The arrow related keys are now handled by Shortcuts as Greg mentioned, which is at the EditableText level, so without an EditableText I wouldn't expect them to work. The other keys mentioned above are still handled by a RawKeyboard listener in RenderEditable (here), but they should be ported to Shortcuts soon.

    @osaxma Have you tried this on another desktop platform like Windows/Linux? I wonder if it works. I also wonder what TextInputClient is supposed to be doing here and if we need it to receive all of these key events or not.

  7. added
    team-macosOwned by the macOS platform team
    and removed on Jun 5, 2024
  8. flutter-triage-bot commented on Jun 5, 2024

    @flutter-triage-bot

    The triaged-desktop label is irrelevant if there is no team-desktop label or fyi-desktop label.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work lista: desktopRunning on desktopa: text inputEntering text in a text field or keyboard related problemsa: typographyText rendering, possibly libtxtengineflutter/engine related. See also e: labels.found in release: 2.3Found to occur in 2.3has reproducible stepsThe issue has been confirmed reproducible and is ready to work onplatform-macosBuilding on or for macOS specificallyteam-macosOwned by the macOS platform teamtriaged-macosTriaged by the macOS platform team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions