Skip to content

Conversation

@matthew-carroll
Copy link
Contributor

@matthew-carroll matthew-carroll commented Jun 26, 2019

Problem:
Flutter is not setup to handle backgrounding and then foregrounding an app when an input control has focus. The issue is visible in the new embedding because the new embedding loosens platform restrictions around FlutterActivity continuity.

Repro Steps:

  • Run an app with a TextField
  • Give focus to the TextField
  • Press Android's home button
  • Switch back to the app
  • Tap the TextField to give it focus again
  • An error appears in the logs.

The issue is that Flutter is unaware that the app was backgrounded and therefore expects the same input connection to exist when re-foregrounded. But on the Android side, the input connection was lost when the app was backgrounded and therefore needs to be recreated when foregrounded again.

Solution:
When FlutterActivity goes to the background and detaches from the FlutterEngine instance, instruct the framework to clear the input client. By clearing it, when the user taps on the control after being re-foregrounded, Flutter will request a new input connection and things should continue as expected.

Corresponding Engine PR:
flutter/engine#9498

@yjbanov yjbanov requested review from mdebbar and removed request for yjbanov June 26, 2019 14:33
@Piinks Piinks added a: text input Entering text in a text field or keyboard related problems framework flutter/packages/flutter repository. See also f: labels. labels Jun 26, 2019
@amirh
Copy link
Contributor

amirh commented Jun 26, 2019

I think it's worth adding a unit test, maybe:

  1. Tap on a TextField
  2. invoke the engine initiated TextInput.clearClient
  3. verify the the framework invoked TextInput.clearClient
  4. tap the text field again
  5. verify that a client is attached and that the keyboard is showing.

Writing this scenario I realize using the exact same method name TextInput.clearClient for different things on the different channel directions may be a little confusing, maybe consider renaming it on one of the directions.

@matthew-carroll
Copy link
Contributor Author

Writing this scenario I realize using the exact same method name TextInput.clearClient for different things on the different channel directions may be a little confusing, maybe consider renaming it on one of the directions.

@amirh the reason I named it the same was because, as far as I could tell, the "clearClient" request is the same request. The only difference is that the request can now originate from either the framework or the platform. Since both sides maintain state related to an input client, that seemed reasonable to me. Did I miss anything?

PS - I'll try to add the test.

@amirh
Copy link
Contributor

amirh commented Jun 26, 2019

I was just a little confused by that name. In my mental model the framework is the one who's making the decisions regarding the current text client, and the engine is executing whatever the framework says. I feel like it's still the case with the new "clearClient" we added (while the engine could clear the client locally, it notified the framework and waits for the framework to ask for the client to be cleared). Under this mental model I'd maybe call the new method "connectionLost" or maybe something with the word "notify".

Just a suggestion.

@matthew-carroll
Copy link
Contributor Author

After chatting with @xster I'm going to pause this PR to see if we might find a framework-level resolution by way of platform lifecycle events. If not, then I'll return to these PRs, fix them up, and merge them in.

@goderbauer
Copy link
Member

goderbauer commented Jul 3, 2019

(PR triage): Sound like this needs further discussion? Can we close the PR for now (since it is not really actionable right now) and move the discussion to an issue?

@matthew-carroll
Copy link
Contributor Author

Closing this PR as per @goderbauer's comment. We may need a more holistic solution to this issue. Will follow up once we've figured that out.

@matthew-carroll
Copy link
Contributor Author

After discussion with @xster and @goderbauer I'm re-opening this PR because this approach seems reasonable for the specific issue in question.

@matthew-carroll matthew-carroll force-pushed the 35054_text_input_error_when_home_and_back branch from 0372083 to ccc1aef Compare July 13, 2019 01:01
@matthew-carroll
Copy link
Contributor Author

I updated the PR with a different approach that utilizes InputConnection#closeConnection() instead of FlutterView destruction. This change in approach now solves the original but, as well as a bug where giving focus to a regular Android View did not result in Flutter UI from losing focus, or clearing of Flutter's input connection.

I also renamed the Android side message from 'clearClient' to 'onConnectionClosed'.

I'm still dubious about where and how I'm invoking unfocus(). If anyone can see a more canonical place to adjust focus, I'd like to hear it.

Copy link
Member

@xster xster left a comment

Choose a reason for hiding this comment

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

Let's add tests

Copy link
Member

Choose a reason for hiding this comment

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

I tend to think this might be a bit too all-encompassing and doesn't leave us room to expand later (if we're implying that the only reason why this object would call you back is to disconnect). I'd just add an explicit listener.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Doesn't ChangeNotifier mean "something has changed, query me to determine what you need to do"?

Copy link
Member

Choose a reason for hiding this comment

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

add a comment here? i.e. we think it's safe to assume that the platform already hide what needed to hiding if the platform calls us to tell us that we disconnected?

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'm not sure I understand...the line you commented is the one that actually does hide the keyboard. Did you mean that we should comment from the other place where _onConnectionClosed() is invoked? or something else?

Copy link
Member

Choose a reason for hiding this comment

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

This call seems reasonable. @HansMuller @gspencergoog any concerns for calling this when the platform disconnects the focus from the Flutter view?

Copy link
Contributor

Choose a reason for hiding this comment

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

This seems fine to me.

@matthew-carroll matthew-carroll force-pushed the 35054_text_input_error_when_home_and_back branch from ccc1aef to 38c3123 Compare July 19, 2019 00:20
@matthew-carroll
Copy link
Contributor Author

After some investigating, the failing test is here:
https://github.com/flutter/flutter/blob/master/packages/flutter/test/material/text_field_test.dart#L1015

The specific line in this PR that causes the failure is here:
https://github.com/flutter/flutter/pull/35100/files#diff-2d74aabcc309c260810a6cca572e134eR1225

I'm unclear on the connection between this PR and the failing test so I reached out to @chunhtai for help fixing this PR.

@chunhtai
Copy link
Contributor

chunhtai commented Aug 8, 2019

After some investigating, the failing test is here:
https://github.com/flutter/flutter/blob/master/packages/flutter/test/material/text_field_test.dart#L1015

The specific line in this PR that causes the failure is here:
https://github.com/flutter/flutter/pull/35100/files#diff-2d74aabcc309c260810a6cca572e134eR1225

I'm unclear on the connection between this PR and the failing test so I reached out to @chunhtai for help fixing this PR.

When textfield switch from non-readonly to readonly, Textfield will close the input connection if it is present. In your pr, it will listen to the connection close which will also unfocus the editable text and that will cause the selection overlay to be hidden. This is why the test is failing.

The correct behavior is from non-readonly to readonly transition, the input connection should be close, but it should still have focus as the selectionoverlay should still present in readonly mode. Is there a reason why we want to unfocus when the input connection closes?

@matthew-carroll
Copy link
Contributor Author

Is there a reason why we want to unfocus when the input connection closes?

WRT to this PR, the reason for that behavior is that when the input connection closes on the platform, it means that text editing has ended. The keyboard should disappear and any cursor should disappear as well. Otherwise, it looks like the text input is still legitimate even though no input connection exists. My perception of this situation is that the way we should do this is by unfocusing the text field.

Based on the intended behavior that you mentioned, it sounds like a text field should be able to receive focus without an input connection, but I'm surprised by this. Are you sure that's the desired behavior? What does it mean for a text field to be focused if it can't receive text input? And how do we differentiate between situations where we should open a text input connection vs not open a text input connection if focus is unrelated to text input?

@chunhtai
Copy link
Contributor

chunhtai commented Aug 9, 2019

Based on the intended behavior that you mentioned, it sounds like a text field should be able to receive focus without an input connection, but I'm surprised by this. Are you sure that's the desired behavior? What does it mean for a text field to be focused if it can't receive text input? And how do we differentiate between situations where we should open a text input connection vs not open a text input connection if focus is unrelated to text input?

It can be focused to receive text selection. and selection (long press on text) does not necessary required an input connection. The reason why we only have selection when it is focused is that we want to hide the selection if the textfield lose focus.

@gspencergoog
Copy link
Contributor

gspencergoog commented Aug 9, 2019

Also, we will eventually need to support keyboard navigation of the selection (e.g. SHIFT RIGHT_ARROW to extend the selection), and in order to get key events from hardware keyboards, the widget needs to be focused.

FocusNodes already have a keyboard token that differentiates between explicit user action and "automatic" focus, and it's up to the widget to consume that token if it wants to open the soft keyboard.

@goderbauer
Copy link
Member

(PR triage): @matthew-carroll Do you still have plans for this PR?

@matthew-carroll
Copy link
Contributor Author

yes

@matthew-carroll matthew-carroll force-pushed the 35054_text_input_error_when_home_and_back branch from 38c3123 to aee8753 Compare August 16, 2019 21:30
@matthew-carroll
Copy link
Contributor Author

@gspencergoog @chunhtai I removed the explicit unfocus and the existing tests pass again.

Copy link
Contributor

@gspencergoog gspencergoog left a comment

Choose a reason for hiding this comment

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

32384589-a60f0e74-c078-11e7-9bc1-e5b5287aea9d

@chunhtai
Copy link
Contributor

It will be good to have a test or two to guard it.

@yjbanov
Copy link
Contributor

yjbanov commented Aug 19, 2019

/cc @nturgut

@matthew-carroll
Copy link
Contributor Author

@chunhtai I pushed a Dart test in text_input_test.dart. I attempted to add one at the textfield widget layer, too, but discovered that the way we fake text input makes such a test superfluous. We essentially bypass the channel behavior of text_input, which is the whole point of the test. I chatted with @gspencergoog about this and he also found the same limitation. So I don't think we can test at that level.

Copy link
Contributor

@chunhtai chunhtai left a comment

Choose a reason for hiding this comment

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

LGTM

@matthew-carroll matthew-carroll merged commit 324fe20 into flutter:master Aug 19, 2019
matthew-carroll added a commit to matthew-carroll/flutter that referenced this pull request Aug 29, 2019
matthew-carroll added a commit that referenced this pull request Aug 30, 2019
@tvolkert
Copy link
Contributor

tvolkert commented Sep 3, 2019

Since this was reverted, is there a corresponding issue that this originally fixed that needs to be re-opened?

@matthew-carroll
Copy link
Contributor Author

The issue is still open. I never got around to closing it: #35054

@smitthakkar1
Copy link

I am still seeing the issue. is there any workaround ?

@xster
Copy link
Member

xster commented Oct 30, 2019

For posterity, Matt's analysis for the cause of the revert from #39523

For future reference, my current theory is this. On the engine side, we watch for Android closing the input connection so that we can forward that info to Flutter. When I implemented that behavior, I assumed that Android only closed connections when input is truly lost for a given input connection. However, based on carefully watching the Flutter gallery app for its phone number field, I think that what Android does is initially show a regular keyboard, and then Flutter tells Android to switch to a number keyboard, which, I think, causes Android to report the input connection is closed before re-opening the input connection with the new type of keyboard. This may be the root cause of the regression. Maybe the embedding's existing logic for opening an input connection is slightly wrong and needs to be updated.

@noborder
Copy link

Has the issue been settled yet

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 2, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

a: text input Entering text in a text field or keyboard related problems framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.