Repository: flutter
Issue: TextInputClient updateEditingValue giving back collapsed selection on Android emulator
Labels: a: text input,platform-android,framework,f: material design,has reproducible steps,P2,found in release: 3.7,team-design,triaged-design

I'm trying to write a Rich Text Editor, but have problems with the text input connection. I'm using `ExtendSelectionByCharacterIntent` to expand my selection to an uncollapsed one, but always get back the collapsed from remote. I've managed to create a little sample that should explain the problem...

I'm using the Android emulator on Android Studio.

1. Execute `flutter run` on the code sample
2. Press Shift+RightArrow to expand selection
3. See console log

**Expected results:**
[log] Updating remote with current value:
[log] TextEditingValue(text: ┤Dummy value├, selection: TextSelection(baseOffset: 0, extentOffset: 1, isDirectional: false), composing: TextRange(start: 0, end: 5))
[log] Received update from remote:
[log] TextEditingValue(text: ┤Dummy value├, selection: TextSelection(baseOffset: 0, extentOffset: 1, isDirectional: false), composing: TextRange(start: 0, end: 5))

**Actual results:**
[log] Updating remote with current value:
[log] TextEditingValue(text: ┤Dummy value├, selection: TextSelection(baseOffset: 0, extentOffset: 1, isDirectional: false), composing: TextRange(start: 0, end: 5))
[log] Received update from remote:
[log] TextEditingValue(text: ┤Dummy value├, selection: TextSelection(baseOffset: 0, extentOffset: 1, isDirectional: false), composing: TextRange(start: -1, end: -1))
[log] Received update from remote:
[log] TextEditingValue(text: ┤Dummy value├, selection: TextSelection.collapsed(offset: 1, affinity: TextAffinity.downstream, isDirectional: false), composing: TextRange(start: -1, end: -1))
[log] Received update from remote:
[log] TextEditingValue(text: ┤Dummy value├, selection: TextSelection.collapsed(offset: 1, affinity: TextAffinity.downstream, isDirectional: false), composing: TextRange(start: 0, end: 5))

<details>
<summary>Code sample</summary>

```
import 'dart:developer';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class RichEditor extends StatefulWidget {
  const RichEditor({super.key});

  @override
  State<RichEditor> createState() => _RichEditorState();
}

class _RichEditorState extends State<RichEditor> implements TextInputClient {
  late final Map<Type, Action<Intent>> _actions = <Type, Action<Intent>>{
    ExtendSelectionByCharacterIntent:
        CallbackAction<ExtendSelectionByCharacterIntent>(
            onInvoke: _extendSelection),
  };
  final FocusNode _focusNode = FocusNode();
  TextInputConnection? _textInputConnection;

  TextEditingValue _currentEditingValue = const TextEditingValue(
    text: 'Dummy value',
    selection: TextSelection.collapsed(offset: 0),
    composing: TextRange(start: 0, end: 5),
  );

  bool get hasConnection =>
      _textInputConnection != null && _textInputConnection!.attached;

  @override
  void initState() {
    super.initState();
    _focusNode.addListener(_handleFocusChanged);
  }

  @override
  void dispose() {
    _closeConnectionIfNeeded();
    _focusNode.removeListener(_handleFocusChanged);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Actions(
      actions: _actions,
      child: Focus(
        focusNode: _focusNode,
        autofocus: true,
        child: Container(),
      ),
    );
  }

  void _handleFocusChanged() {
    _openOrCloseConnection();
  }

  void _openOrCloseConnection() {
    if (_focusNode.hasFocus && _focusNode.consumeKeyboardToken()) {
      _openConnectionIfNeeded();
    } else if (!_focusNode.hasFocus) {
      _closeConnectionIfNeeded();
    }
  }

  void _openConnectionIfNeeded() {
    if (!hasConnection) {
      _textInputConnection = TextInput.attach(
        this,
        const TextInputConfiguration(
          inputType: TextInputType.multiline,
          readOnly: false,
          inputAction: TextInputAction.newline,
        ),
      );

      _textInputConnection!.setEditingState(currentTextEditingValue!);
    }

    _textInputConnection!.show();
  }

  void _closeConnectionIfNeeded() {
    if (!hasConnection) {
      return;
    }
    _textInputConnection!.close();
    _textInputConnection = null;
  }

  void _updateRemoteValueIfNeeded() {
    if (!hasConnection) {
      return;
    }

    log('Updating remote with current value:');
    log(currentTextEditingValue.toString());

    _textInputConnection!.setEditingState(currentTextEditingValue!);
  }

  void _extendSelection(ExtendSelectionByCharacterIntent intent) {
    _currentEditingValue = _currentEditingValue.copyWith(
      selection: const TextSelection(baseOffset: 0, extentOffset: 1),
    );
    _updateRemoteValueIfNeeded();
  }

  @override
  TextEditingValue? get currentTextEditingValue => _currentEditingValue;

  @override
  void updateEditingValue(TextEditingValue value) {
    log('Received update from remote:');
    log(value.toString());
  }

  @override
  void connectionClosed() {
    // No op
  }

  @override
  AutofillScope? get currentAutofillScope => throw UnimplementedError();

  @override
  void didChangeInputControl(
      TextInputControl? oldControl, TextInputControl? newControl) {
    // No op
  }

  @override
  void insertTextPlaceholder(Size size) {
    // No op
  }

  @override
  void performAction(TextInputAction action) {
    // No op
  }

  @override
  void performPrivateCommand(String action, Map<String, dynamic> data) {
    // No op
  }

  @override
  void performSelector(String selectorName) {
    // No op
  }

  @override
  void removeTextPlaceholder() {
    // No op
  }

  @override
  void showAutocorrectionPromptRect(int start, int end) {
    // No op
  }

  @override
  void showToolbar() {
    // No op
  }

  @override
  void updateFloatingCursor(RawFloatingCursorPoint point) {
    // No op
  }
}
```

</details>

<details>
  <summary>Logs</summary>

```
D/InputConnectionAdaptor( 7706): The input method toggled cursor monitoring on
[log] Updating remote with current value:
[log] TextEditingValue(text: ┤Dummy value├, selection: TextSelection(baseOffset: 0, extentOffset: 1, isDirectional: false), composing: TextRange(start: 0, end: 5))
[log] Received update from remote:
[log] TextEditingValue(text: ┤Dummy value├, selection: TextSelection(baseOffset: 0, extentOffset: 1, isDirectional: false), composing: TextRange(start: -1, end: -1))
[log] Received update from remote:
[log] TextEditingValue(text: ┤Dummy value├, selection: TextSelection.collapsed(offset: 1, affinity: TextAffinity.downstream, isDirectional: false), composing: TextRange(start: -1, end: -1))
[log] Received update from remote:
[log] TextEditingValue(text: ┤Dummy value├, selection: TextSelection.collapsed(offset: 1, affinity: TextAffinity.downstream, isDirectional: false), composing: TextRange(start: 0, end: 5))

```

</details>


<details>
  <summary>Flutter Doctor</summary>

```
[√] Flutter (Channel stable, 3.7.0, on Microsoft Windows [Versione 10.0.19044.2486], locale it-IT)
    • Flutter version 3.7.0 on channel stable at C:\flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision b06b8b2710 (6 days ago), 2023-01-23 16:55:55 -0800
    • Engine revision b24591ed32
    • Dart version 2.19.0
    • DevTools version 2.20.1

[X] Windows Version (Unable to confirm if installed Windows version is 10 or greater)

[√] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
    • Android SDK at C:\Users\Alessandro\AppData\Local\Android\sdk
    • Platform android-33, build-tools 30.0.3
    • Java binary at: C:\Program Files\Android\Android Studio\jre\bin\java
    • Java version OpenJDK Runtime Environment (build 11.0.12+7-b1504.28-7817840)
    • All Android licenses accepted.

[√] Chrome - develop for the web
    • Chrome at C:\Program Files\Google\Chrome\Application\chrome.exe

[√] Visual Studio - develop for Windows (Visual Studio Community 2019 16.11.21)
    • Visual Studio at C:\Program Files (x86)\Microsoft Visual Studio\2019\Community
    • Visual Studio Community 2019 version 16.11.33027.164
    • Windows 10 SDK version 10.0.19041.0

[√] Android Studio (version 2021.2)
    • Android Studio at C:\Program Files\Android\Android Studio
    • 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.12+7-b1504.28-7817840)

[√] VS Code (version 1.71.2)
    • VS Code at C:\Users\Alessandro\AppData\Local\Programs\Microsoft VS Code
    • Flutter extension version 3.48.0

[√] Connected device (4 available)
    • Android SDK built for x86 (mobile) • emulator-5554 • android-x86    • Android 11 (API 30) (emulator)
    • Windows (desktop)                  • windows       • windows-x64    • Microsoft Windows [Versione 10.0.19044.2486]
    • Chrome (web)                       • chrome        • web-javascript • Google Chrome 109.0.5414.120
    • Edge (web)                         • edge          • web-javascript • Microsoft Edge 109.0.1518.55

[√] HTTP Host Availability
    • All required HTTP hosts are available
```

</details>