Skip to content

iOS: Japanese IME 'next candidate' long-press causes repeated character insertion #2710

@meny1122

Description

@meny1122

Description

When using the Japanese keyboard on iOS, long-pressing the "next candidate" (次候補) button causes repeated character insertion instead of cycling through candidates normally.

Root Cause

In updateRemoteValueIfNeeded() (raw_editor_state_text_input_client_mixin.dart), the method sends the editing state back to the platform with composing=(-1,-1) even while the IME is actively composing. The iOS platform interprets this as "composing text has been committed" and re-sends the composing text as new input, creating a feedback loop that inserts duplicate characters.

Flow of the bug:

  1. User types Japanese text → IME composing range is active (e.g., composing=(10,12))
  2. Controller change triggers updateRemoteValueIfNeeded()
  3. Method builds actualValue with composing=(-1,-1) (hardcoded TextRange.empty)
  4. Sends this value to platform via setEditingState()
  5. Platform sees composing=(-1,-1) → thinks composing text was committed
  6. Platform re-sends the composing text as new input via updateEditingValue()
  7. This triggers another controller change → goto step 2
  8. Result: infinite loop of character insertion

Proposed Fix

During active composing, handle updates selectively instead of always sending composing=(-1,-1):

  • Text changed → skip sending (let the platform drive state, prevents the loop)
  • Selection-only changed → send with composing range preserved (allows cursor movement within composing region to update conversion candidates)
  • Nothing changed → skip (existing equality check)
if (composingRange.isValid && composingRange.end <= value.text.length) {
  if (actualValue.text == _lastKnownRemoteTextEditingValue!.text &&
      actualValue.selection != _lastKnownRemoteTextEditingValue!.selection) {
    _lastKnownRemoteTextEditingValue = actualValue;
    _textInputConnection!.setEditingState(actualValue);
  }
  return;
}

Verified behavior

  1. Long-press "next candidate" → no repeated character insertion ✓
  2. Cursor movement within composing region → candidates update correctly ✓
  3. Normal Japanese input (compose → commit) → works ✓
  4. Candidate selection by tap → works ✓

Steps to Reproduce

  1. Open a QuillEditor on iOS
  2. Switch to Japanese keyboard (e.g., Kana input)
  3. Type some Japanese text (e.g., "なた")
  4. Long-press the "次候補" (next candidate) button on the keyboard
  5. Expected: Candidates cycle through without inserting text
  6. Actual: Characters are repeatedly inserted, filling the editor

Environment

  • flutter_quill version: 11.5.0
  • Flutter: 3.38.9
  • Platform: iOS
  • Keyboard: Japanese (Kana)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions