-
Notifications
You must be signed in to change notification settings - Fork 29.8k
Respond to TextInputClient.reattach messages. #43959
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
This test is .. very ugly. But it's a test. |
| final _TextInputClientHandler _clientHandler = _TextInputClientHandler(); | ||
| final TextInputClientHandler _clientHandler = TextInputClientHandler( | ||
| SystemChannels.textInput, | ||
| null, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make this an optional parameter?
| /// should call [TextInputConnection.close] on the returned | ||
| /// [TextInputConnection]. | ||
| static TextInputConnection attach(TextInputClient client, TextInputConfiguration configuration) { | ||
| static TextInputConnection attach(TextInputClient client) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a breaking change, no?
| return connection; | ||
| } | ||
|
|
||
| static void _attach(TextInputClientHandler handler, TextInputConnection connection) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should probably be some documentation on what the difference between attach and _attach is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
| /// | ||
| /// This configuration is requested when establishing or re-establishing the | ||
| /// connection between the [TextInput] and the engine. | ||
| TextInputConfiguration createConfiguration(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This also looks like a breaking change? Implementors/Subclasses of this class will have to implement a new method?
| class TextInputClientHandler { | ||
| /// Creates a [TextInputClientHandler]. | ||
| @visibleForTesting | ||
| TextInputClientHandler(this._channel, this._currentConnection) : assert(_channel != null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The arguments are only there for testing, right? I wonder if we should default them to what we need them in production.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm refactoring the test.
| final String method = methodCall.method; | ||
|
|
||
| // Reattach request needs to be handled regardless of the client ID. | ||
| if (method == 'TextInputClient.reattach') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same question here as the engine side PR. Can we call this something like requestExistingInputState or requestInputConfiguration so that we can avoid implying a previous attachment and express the goal of the message?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
goderbauer
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some other notes:
- Do Android and Framework agree on the state of the text field after re-attach, i.e. with regards to the text that's present in the text field?
- Can you also double check that this doesn't break textfields that switch the keyboard (as we have seen with the previous attempt)?
- We need separate API for this and #43466? Or can we consolidate? (Maybe at least only do one breaking change announcement for both)
| final String method = methodCall.method; | ||
|
|
||
| // Reattach request needs to be handled regardless of the client ID. | ||
| if (method == 'TextInputClient.reattach') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you also add the new message to the documentation in system_channels.dart
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
matthew-carroll
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll defer to @goderbauer for the LGTM on this because I'm less familiar with the Dart side of input connections. But nothing jumps out at me.
| /// second argument is a [String] consisting of the stringification of one | ||
| /// of the values of the [TextInputAction] enum. | ||
| /// | ||
| /// * `TextInputClient.requestExistingInputState`: The embedding may have |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's document what should happen if there's nothing to send.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
| if (method == 'TextInputClient.requestExistingInputState') { | ||
| assert(_currentConnection._client != null); | ||
| _attach(_currentConnection); | ||
| if (_lastTextEditingValue != null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it seem odd that we cache the lastTextEditingValue, but don't cache the configuration?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just caching them all now. I think this is probably cleaner and it's no longer breaking.
| @@ -1,17 +0,0 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe revert this and clean it up in a separate PR, potentially by adding something to gitignore?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Going to file a separate PR, this was unintentional. Cleaned it up for now here.
9686425 to
ed02d70
Compare
ed02d70 to
629c3d9
Compare
goderbauer
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
I like that all the channel calls are now encapsulated in one object and not all over the place.
| // Reattach request needs to be handled regardless of the client ID. | ||
| if (method == 'TextInputClient.requestExistingInputState') { | ||
| assert(_currentConnection._client != null); | ||
| _attach(_currentConnection, _currentConfiguration); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was wondering here if _currentConfiguration could ever be null at this point. Maybe add an assert(_currentConfiguration != null) above this one, possibly with a comment saying that we always have a configuration if we have a connection?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have an early return at line 926 if _currentConfiguration == null.
| return; | ||
| final String method = methodCall.method; | ||
|
|
||
| // Reattach request needs to be handled regardless of the client ID. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update this comment? Since the method is no longer called "reattach"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
|
|
||
| setUp(() { | ||
| fakeTextChannel = FakeTextChannel((MethodCall call) async { | ||
| return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: do you actually need the return? Just an empty body is not enough?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought that would make the analyzer unhappy, I was wrong. Done
|
|
||
| test('text input client handler responds to reattach with setClient', () async { | ||
| TextInput.attach(client, client.configuration); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe expect here that there has been one method call to ensure that requestExistingInputState only calls setClient once and not twice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
| final TextInputConnection connection = TextInput.attach(client, client.configuration); | ||
| const TextEditingValue editingState = TextEditingValue(text: 'foo'); | ||
| connection.setEditingState(editingState); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
|
|
||
| @override | ||
| void updateEditingValue(TextEditingValue value) => throw UnimplementedError(); | ||
| @override |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
uber-nit: add an empty line above this one for constancy?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
| final String expectedString = utf8.decode(expectedData.buffer.asUint8List()); | ||
|
|
||
| if (outgoingString != expectedString) { | ||
| print('''Index $i did not match: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would format a little nicer as:
print(
'Index $i did not match:\n'
' actual: ${outgoingCalls[i]}\n'
' expected: ${calls[i]}'
);There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, done.
|
/cc @nturgut for review of merge |
nturgut
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the merge Dan!
| if (_currentTextEditingValue != null) { | ||
| _setEditingState(_currentTextEditingValue); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Quick question: in Android or in general?
In Flutter for Web, this is the main part that we are syncing the state from Framework to Platform. It works for the web engine so far.
Let's debug and try to see why it is not the case for Android.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Android is the only platform that would go down this codepath today. It shouldn't be a problem for web.
See the linked PR for more details - I don't think this is a problem for web, but it is a problem for Android where the engine side loses state.
Description
Companion to flutter/engine#13474
When the new Android embedding detaches from the engine (e.g. because it goes to the background), it loses state related to text input client(s).
It's being updated to send this message to the framework so that the framework can reattach the text input client, and the keyboard can continue to work.
Related Issues
This and the related engine PR will combine to resolve #35054
Tests
I need to figure out how to test this. Testing will require some refactoring, I'm thinking at the very lease making
_TextInputClientHandler@visibleForTesting, or figure out a way to have the test binding simulate a message coming from the engine for this. For now I just want it up so reviewers of the engine side can see.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.///).flutter analyze --flutter-repo) does not report any problems on my PR.Breaking Change
Does your PR require Flutter developers to manually update their apps to accommodate your change?