Skip to content

Conversation

@LongCatIsLooong
Copy link
Contributor

@LongCatIsLooong LongCatIsLooong commented Sep 28, 2020

Description

Historically in text input plugins we send the TextEditingValues we received from the framework back to the framework, in order to trigger user input callbacks for text selection menu operations (flutter/engine@a90d17b). This has caused a few issues including infinite loops.

Related Issues

Fixes #66144

Tests

I added the following tests:

  • EditableText does not send editing values more than once

Checklist

Before you create this PR, confirm that it meets all requirements listed below by checking the relevant checkboxes ([x]). This will ensure a smooth and quick review process.

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I signed the CLA.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I updated/added relevant documentation (doc comments with ///).
  • All existing and new tests are passing.
  • The analyzer (flutter analyze --flutter-repo) does not report any problems on my PR.
  • I am willing to follow-up on review comments in a timely manner.

Breaking Change

Did any tests fail when you ran them? Please read Handling breaking changes.

  • No, no existing tests failed, so this is not a breaking change.

@flutter-dashboard flutter-dashboard bot added framework flutter/packages/flutter repository. See also f: labels. work in progress; do not review labels Sep 28, 2020
@LongCatIsLooong LongCatIsLooong force-pushed the editable-text-updateEditingValue branch from 8cb420f to c3fc5cc Compare September 28, 2020 23:27
@LongCatIsLooong LongCatIsLooong force-pushed the editable-text-updateEditingValue branch from c3fc5cc to 21cc3c1 Compare September 29, 2020 17:00
@LongCatIsLooong LongCatIsLooong force-pushed the editable-text-updateEditingValue branch 5 times, most recently from cbda64f to 60b7a93 Compare September 29, 2020 19:51
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit questionable. For example if inputFormatters is updated and the same TextEditingValue is received a second time it should probably not shortcircuit.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was recently added in #65754. I'm not sure what's up with the numeric keyboard problem.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit questionable. For example if inputFormatters is updated and the same TextEditingValue is received a second time it should probably not shortcircuit.

Yes, the situation you said should not be short-circuited. Maybe we should go through _formatAndSetValue if we receive duplicate values at this situation. Can we add a test case to test the situation you said?
There is also a scenario where duplicate values will be received. When the keyboard is hidden, the engine will send the current value again. #66036

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to update the formatter (and reapply it) when _textDirection changes?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's possible to write a formatter that does something different depending on the text direction, right? If so I would say yes we need to reapply it. But I can't think of a real world example. Maybe open an issue for it for another PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't take a very careful look but _WhitespaceDirectionalityFormatter maintains states and may need to be re-instantiated and re-applied.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic here is a bit different. We used to skip the _whitespaceFormatter when there's no other formatters.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think flutter/engine#20160 prevents the repeat on iOS. The android text input plugin still does that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doing this because this seems to be the path of least resistance.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds good to me. At some point did you say that you plan to implement the same kind of idea as flutter/engine#20160 in Android? Or is the plan to leave it as-is?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing the formatter caching altogether. I think a TextInputFormatter is allowed to have states? If that's the case we shouldn't cache the formatter outputs.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could imagine a formatter that has state. Not sure what Flutter's opinion on that is, though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should inputFormatters work retroactively?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question... I'm not sure. I guess it would be easy for the user to apply their input formatter to the existing text if they want to. Maybe some users are doing that now. I guess I don't have a strong reason to change it.

@LongCatIsLooong LongCatIsLooong force-pushed the editable-text-updateEditingValue branch from 60b7a93 to d5f5bd8 Compare September 29, 2020 20:13
Copy link
Contributor

@justinmc justinmc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a great clean up, we really needed this. The docs are much more useful now. I was constantly confused by how all of this engine/framework communication happens.

Most of my comments are just nits or attempts to answer some of your questions.

///
/// See also:
///
/// * [EditableTextState.textEditingValue]: an implementation that appleis
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo on "applies".

/// See also:
///
/// * [TextInput.attach]
///
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No empty line needed here I think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds good to me. At some point did you say that you plan to implement the same kind of idea as flutter/engine#20160 in Android? Or is the plan to leave it as-is?

/// [TextInputClient]'s [AutofillScope], and the [AutofillScope] will further
/// relay the value to the correct [TextInputClient].
///
/// When synchronizing the [TextEditingValue]s, the communication may stuck in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"may stuck" => "may get stuck"

///
/// Used in [_updateRemoteEditingValueIfNeeded] to determine whether the
/// remote value is outdated and needs updating.
TextEditingValue? _lastKnownRemoteTextEditingValue;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 for the name improvement.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a great job to remove those caches.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was recently added in #65754. I'm not sure what's up with the numeric keyboard problem.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's possible to write a formatter that does something different depending on the text direction, right? If so I would say yes we need to reapply it. But I can't think of a real world example. Maybe open an issue for it for another PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could imagine a formatter that has state. Not sure what Flutter's opinion on that is, though.

// Before triggering the [_value] change notifier, we want to make sure
// [_updateRemoteEditingValueIfNeeded] is muted, so we can consolidate the
// changes and send them to the engine in one batch.
_muteRemoteUpdate = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the mute idea might have the potential to create confusing behavior if future editors aren't careful. I can't think of a better architecture though. I see why we need to batch these.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am sorry that I did not understand the purpose of this code, can you guys explain what were batched? thanks :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because Is possible to modify this value on widget.onChanged?

Copy link
Contributor Author

@LongCatIsLooong LongCatIsLooong Oct 3, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah onChanged and the listeners registered to TextEditingController both may update the editing value.

Should I call it beginBatchEdit & endBatchEdit, and allow nesting?

Copy link
Member

@xu-baolin xu-baolin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

    // For consistency with Android behavior, send an update to the framework.
    [self updateEditingState];

The comments say consistency with Android, but sometimes the Android does not do this, is that right? Is it possible that we make modify it here?

Should we determine a data synchronization principle? For example, both sides do not respond to the changes notified by the other side, but only notify the local changes to remote. Otherwise, our scene will become complicated and difficult to maintain.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit questionable. For example if inputFormatters is updated and the same TextEditingValue is received a second time it should probably not shortcircuit.

Yes, the situation you said should not be short-circuited. Maybe we should go through _formatAndSetValue if we receive duplicate values at this situation. Can we add a test case to test the situation you said?
There is also a scenario where duplicate values will be received. When the keyboard is hidden, the engine will send the current value again. #66036

///
/// ## Handling User Input
///
/// Currently the user may change the text this widget contains via keyboard or
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The user also can change the text by the controller directly. If change the text by controller [inpitFormatters] will not be applied. Should [inpitFormatters] work here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "user" here refers to end-users (that use the app). I think inputFormatters is meant for sanitizing user input. If the developer decides to change the text programmatically then they should be responsible for making sure the new text is valid.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh in the documentation it only says "when the text input changes": https://api.flutter.dev/flutter/material/TextField/inputFormatters.html. I think we should make that clear.


// If by coincidence the text input plugin sends the same value back,
// do nothing.
state.updateEditingValue(const TextEditingValue(text: 'remote listener onChanged listener'));
Copy link
Member

@xu-baolin xu-baolin Sep 30, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the inputFormatters be applied?
This happens to be the scene we discussed above
This is a bit questionable. For example if inputFormatters is updated and the same TextEditingValue is received a second time it should probably not shortcircuit.

If the answer is yes and the iOS platform always returns this value, is it possible to cause an endless loop?

I think the answer depends on the situation. If it is the value sent back immediately (like iOS), it should not be applied, but if it is the user input later, it should be applied.

We really can’t let the plug-in send back the value to the framework, which will make the problem very tricky.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With flutter/engine#20160 the ios input plugin should never send values back if the change is initiated by the framework.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see.
In addition, should the inputFormatters be applied here?

Copy link
Contributor Author

@LongCatIsLooong LongCatIsLooong Oct 9, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure. In theory it should. I'll go find out if there's practically reasons about the current input formatter implementation, and open a new PR if there's anything needs changing.

});

testWidgets('input from text selection menu', (WidgetTester tester) async {
await tester.pumpWidget(widget);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This appears to just be the same test case of the previous, although I'll be pleased to hear that I'm missing something :-).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not exactly the same as the previous "input from text input plugin" test, as it tests the textEditingValue setter call path instead of updateEditingValue

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see.

}

// Setting _value here ensures the selection and composing region info is passed.
// Before triggering the [_value] change notifier, we want to make sure
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you comment here why batch processing is needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed wording.

@LongCatIsLooong
Copy link
Contributor Author

LongCatIsLooong commented Oct 9, 2020

@justinmc @xu-baolin @GaryQian
Ready for review again. Updates and notes:

  • It seems the input formatters should trigger a reformat when they change, but currently we're not doing so. I'll try to figure out if there's any practical reasons behind that.
    Update: I'm going to open a sepaprate PR for the text input formatter stuff.

  • "platform text input plugin" vs "system text input". Still not quite sure about the nomenclature. I'd like to think these 2 are different entities, the former is the engine code that implements input handling, and the latter refers to the OS part of text input (i.e., IME/software keyboard, InputMethodManager). But I don't think developers need to be aware of the difference. Leaning towards system text input as it's more self-explanatory. That API doc section targets TextInputClient and platform text input plugin authors I think it's ok to distinguish the two.

  • Added beginBatchEdit and endBatchEdit.

Copy link
Contributor

@justinmc justinmc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM but FYI there is a merge conflict.

Linking @xu-baolin's PR #67892, which is related and may affect the text input formatter stuff you mentioned.

I'm on board with the platform/system nomenclature and will try to use it consistently!

@fluttergithubbot fluttergithubbot merged commit daa6b2c into flutter:master Oct 12, 2020
@LongCatIsLooong LongCatIsLooong deleted the editable-text-updateEditingValue branch October 12, 2020 21:21
@lzhuor
Copy link

lzhuor commented Oct 13, 2020

Hi @justinmc , may I ask will this be patched to 1.22 or it will be in the next minor release (e.g. 1.23)?

renyou added a commit that referenced this pull request Oct 13, 2020
renyou added a commit that referenced this pull request Oct 13, 2020
@justinmc
Copy link
Contributor

It looks like this has been reverted unfortunately, but we'll try to get it back in soon. Watch for another PR linked here.

LongCatIsLooong added a commit to LongCatIsLooong/flutter that referenced this pull request Oct 13, 2020
@lzhuor
Copy link

lzhuor commented Oct 18, 2020

Hi @justinmc , thanks for your help. May I ask is there any update on this issue? I understand it's reverted. Could you please share the new issue / PR related to this fix?

@justinmc
Copy link
Contributor

Sure, this was relanded in #68043 and is currently in master.

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

Labels

framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[iOS] adding autoFillHint to TextFormField breaks FocusScope.of(context).nextFocus()

6 participants