Respond to TextInputClient.reattach messages.#43959
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.
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.
This is a breaking change, no?
| return connection; | ||
| } | ||
|
|
||
| static void _attach(TextInputClientHandler handler, TextInputConnection connection) { |
There was a problem hiding this comment.
There should probably be some documentation on what the difference between attach and _attach is.
| /// | ||
| /// 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.
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.
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.
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.
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?
goderbauer
left a comment
There was a problem hiding this comment.
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.
Can you also add the new message to the documentation in system_channels.dart
matthew-carroll
left a comment
There was a problem hiding this comment.
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.
Let's document what should happen if there's nothing to send.
| if (method == 'TextInputClient.requestExistingInputState') { | ||
| assert(_currentConnection._client != null); | ||
| _attach(_currentConnection); | ||
| if (_lastTextEditingValue != null) { |
There was a problem hiding this comment.
Does it seem odd that we cache the lastTextEditingValue, but don't cache the configuration?
There was a problem hiding this comment.
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.
Maybe revert this and clean it up in a separate PR, potentially by adding something to gitignore?
There was a problem hiding this comment.
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.
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.
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.
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.
Update this comment? Since the method is no longer called "reattach"?
|
|
||
| setUp(() { | ||
| fakeTextChannel = FakeTextChannel((MethodCall call) async { | ||
| return; |
There was a problem hiding this comment.
nit: do you actually need the return? Just an empty body is not enough?
There was a problem hiding this comment.
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.
Maybe expect here that there has been one method call to ensure that requestExistingInputState only calls setClient once and not twice.
| final TextInputConnection connection = TextInput.attach(client, client.configuration); | ||
| const TextEditingValue editingState = TextEditingValue(text: 'foo'); | ||
| connection.setEditingState(editingState); | ||
|
|
|
|
||
| @override | ||
| void updateEditingValue(TextEditingValue value) => throw UnimplementedError(); | ||
| @override |
There was a problem hiding this comment.
uber-nit: add an empty line above this one for constancy?
| final String expectedString = utf8.decode(expectedData.buffer.asUint8List()); | ||
|
|
||
| if (outgoingString != expectedString) { | ||
| print('''Index $i did not match: |
There was a problem hiding this comment.
This would format a little nicer as:
print(
'Index $i did not match:\n'
' actual: ${outgoingCalls[i]}\n'
' expected: ${calls[i]}'
);|
/cc @nturgut for review of merge |
nturgut
left a comment
There was a problem hiding this comment.
Thanks for the merge Dan!
| if (_currentTextEditingValue != null) { | ||
| _setEditingState(_currentTextEditingValue); | ||
| } |
There was a problem hiding this comment.
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.
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?