Commit b9bff6a9 authored by Matt Perry's avatar Matt Perry Committed by GitHub

Only show the keyboard when the user explicitly focuses an Input. (#6713)

Fixes https://github.com/flutter/flutter/issues/6603
parent fb03b313
...@@ -273,8 +273,12 @@ class RawInputState extends ScrollableState<RawInput> implements TextInputClient ...@@ -273,8 +273,12 @@ class RawInputState extends ScrollableState<RawInput> implements TextInputClient
return newScrollOffset; return newScrollOffset;
} }
// True if the focus was explicitly requested last frame. This ensures we
// don't show the keyboard when focus defaults back to the RawInput.
bool _requestingFocus = false;
void _attachOrDetachKeyboard(bool focused) { void _attachOrDetachKeyboard(bool focused) {
if (focused && !_isAttachedToKeyboard) { if (focused && !_isAttachedToKeyboard && _requestingFocus) {
_textInputConnection = TextInput.attach( _textInputConnection = TextInput.attach(
this, new TextInputConfiguration(inputType: config.keyboardType)) this, new TextInputConfiguration(inputType: config.keyboardType))
..setEditingState(_getTextEditingStateFromInputValue(_currentValue)) ..setEditingState(_getTextEditingStateFromInputValue(_currentValue))
...@@ -286,6 +290,7 @@ class RawInputState extends ScrollableState<RawInput> implements TextInputClient ...@@ -286,6 +290,7 @@ class RawInputState extends ScrollableState<RawInput> implements TextInputClient
} }
_clearComposing(); _clearComposing();
} }
_requestingFocus = false;
} }
void _clearComposing() { void _clearComposing() {
...@@ -306,6 +311,9 @@ class RawInputState extends ScrollableState<RawInput> implements TextInputClient ...@@ -306,6 +311,9 @@ class RawInputState extends ScrollableState<RawInput> implements TextInputClient
_textInputConnection.show(); _textInputConnection.show();
} else { } else {
Focus.moveTo(config.focusKey); Focus.moveTo(config.focusKey);
setState(() {
_requestingFocus = true;
});
} }
} }
......
...@@ -14,6 +14,12 @@ void main() { ...@@ -14,6 +14,12 @@ void main() {
mockTextInput.enterText(text); mockTextInput.enterText(text);
} }
Future<Null> showKeyboard(WidgetTester tester) async {
RawInputState editable = tester.state(find.byType(RawInput).first);
editable.requestKeyboard();
await tester.pump();
}
testWidgets('onSaved callback is called', (WidgetTester tester) async { testWidgets('onSaved callback is called', (WidgetTester tester) async {
GlobalKey<FormState> formKey = new GlobalKey<FormState>(); GlobalKey<FormState> formKey = new GlobalKey<FormState>();
String fieldValue; String fieldValue;
...@@ -32,6 +38,7 @@ void main() { ...@@ -32,6 +38,7 @@ void main() {
} }
await tester.pumpWidget(builder()); await tester.pumpWidget(builder());
await showKeyboard(tester);
expect(fieldValue, isNull); expect(fieldValue, isNull);
...@@ -65,6 +72,7 @@ void main() { ...@@ -65,6 +72,7 @@ void main() {
} }
await tester.pumpWidget(builder()); await tester.pumpWidget(builder());
await showKeyboard(tester);
Future<Null> checkErrorText(String testValue) async { Future<Null> checkErrorText(String testValue) async {
enterText(testValue); enterText(testValue);
...@@ -81,7 +89,6 @@ void main() { ...@@ -81,7 +89,6 @@ void main() {
testWidgets('Multiple Inputs communicate', (WidgetTester tester) async { testWidgets('Multiple Inputs communicate', (WidgetTester tester) async {
GlobalKey<FormState> formKey = new GlobalKey<FormState>(); GlobalKey<FormState> formKey = new GlobalKey<FormState>();
GlobalKey<FormFieldState<InputValue>> fieldKey = new GlobalKey<FormFieldState<InputValue>>(); GlobalKey<FormFieldState<InputValue>> fieldKey = new GlobalKey<FormFieldState<InputValue>>();
GlobalKey inputFocusKey = new GlobalKey();
GlobalKey focusKey = new GlobalKey(); GlobalKey focusKey = new GlobalKey();
// Input 2's validator depends on a input 1's value. // Input 2's validator depends on a input 1's value.
String errorText(InputValue input) => fieldKey.currentState.value?.text.toString() + '/error'; String errorText(InputValue input) => fieldKey.currentState.value?.text.toString() + '/error';
...@@ -96,9 +103,7 @@ void main() { ...@@ -96,9 +103,7 @@ void main() {
child: new Block( child: new Block(
children: <Widget>[ children: <Widget>[
new InputFormField( new InputFormField(
autofocus: true, key: fieldKey
key: fieldKey,
focusKey: inputFocusKey,
), ),
new InputFormField( new InputFormField(
validator: errorText, validator: errorText,
...@@ -112,8 +117,7 @@ void main() { ...@@ -112,8 +117,7 @@ void main() {
} }
await tester.pumpWidget(builder()); await tester.pumpWidget(builder());
await tester.pump(); await showKeyboard(tester);
Focus.moveTo(inputFocusKey);
Future<Null> checkErrorText(String testValue) async { Future<Null> checkErrorText(String testValue) async {
enterText(testValue); enterText(testValue);
...@@ -147,6 +151,7 @@ void main() { ...@@ -147,6 +151,7 @@ void main() {
} }
await tester.pumpWidget(builder()); await tester.pumpWidget(builder());
await showKeyboard(tester);
// initial value should be loaded into keyboard editing state // initial value should be loaded into keyboard editing state
expect(mockTextInput.editingState, isNotNull); expect(mockTextInput.editingState, isNotNull);
...@@ -187,7 +192,7 @@ void main() { ...@@ -187,7 +192,7 @@ void main() {
} }
await tester.pumpWidget(builder(false)); await tester.pumpWidget(builder(false));
await tester.pump(); await showKeyboard(tester);
expect(fieldValue, isNull); expect(fieldValue, isNull);
expect(formKey.currentState.hasErrors, isFalse); expect(formKey.currentState.hasErrors, isFalse);
......
...@@ -50,6 +50,12 @@ void main() { ...@@ -50,6 +50,12 @@ void main() {
mockTextInput.enterText(text); mockTextInput.enterText(text);
} }
Future<Null> showKeyboard(WidgetTester tester) async {
RawInputState editable = tester.state(find.byType(RawInput).first);
editable.requestKeyboard();
await tester.pump();
}
// Returns the first RenderEditable. // Returns the first RenderEditable.
RenderEditable findRenderEditable(WidgetTester tester) { RenderEditable findRenderEditable(WidgetTester tester) {
RenderObject root = tester.renderObject(find.byType(RawInput)); RenderObject root = tester.renderObject(find.byType(RawInput));
...@@ -94,6 +100,7 @@ void main() { ...@@ -94,6 +100,7 @@ void main() {
} }
await tester.pumpWidget(builder()); await tester.pumpWidget(builder());
await showKeyboard(tester);
RenderBox findInputBox() => tester.renderObject(find.byKey(inputKey)); RenderBox findInputBox() => tester.renderObject(find.byKey(inputKey));
...@@ -134,6 +141,7 @@ void main() { ...@@ -134,6 +141,7 @@ void main() {
} }
await tester.pumpWidget(builder()); await tester.pumpWidget(builder());
await showKeyboard(tester);
RawInputState editableText = tester.state(find.byType(RawInput)); RawInputState editableText = tester.state(find.byType(RawInput));
...@@ -179,6 +187,7 @@ void main() { ...@@ -179,6 +187,7 @@ void main() {
} }
await tester.pumpWidget(builder()); await tester.pumpWidget(builder());
await showKeyboard(tester);
const String testValue = 'ABC'; const String testValue = 'ABC';
updateEditingState(new TextEditingState( updateEditingState(new TextEditingState(
...@@ -215,6 +224,7 @@ void main() { ...@@ -215,6 +224,7 @@ void main() {
} }
await tester.pumpWidget(builder()); await tester.pumpWidget(builder());
await showKeyboard(tester);
String testValue = 'abc def ghi'; String testValue = 'abc def ghi';
enterText(testValue); enterText(testValue);
...@@ -262,6 +272,7 @@ void main() { ...@@ -262,6 +272,7 @@ void main() {
} }
await tester.pumpWidget(builder()); await tester.pumpWidget(builder());
await showKeyboard(tester);
String testValue = 'abc def ghi'; String testValue = 'abc def ghi';
enterText(testValue); enterText(testValue);
...@@ -337,6 +348,7 @@ void main() { ...@@ -337,6 +348,7 @@ void main() {
} }
await tester.pumpWidget(builder()); await tester.pumpWidget(builder());
await showKeyboard(tester);
String testValue = 'abc def ghi'; String testValue = 'abc def ghi';
enterText(testValue); enterText(testValue);
...@@ -402,6 +414,7 @@ void main() { ...@@ -402,6 +414,7 @@ void main() {
} }
await tester.pumpWidget(builder()); await tester.pumpWidget(builder());
await showKeyboard(tester);
String testValue = 'abc def ghi'; String testValue = 'abc def ghi';
enterText(testValue); enterText(testValue);
...@@ -452,6 +465,7 @@ void main() { ...@@ -452,6 +465,7 @@ void main() {
} }
await tester.pumpWidget(builder(3)); await tester.pumpWidget(builder(3));
await showKeyboard(tester);
RenderBox findInputBox() => tester.renderObject(find.byKey(inputKey)); RenderBox findInputBox() => tester.renderObject(find.byKey(inputKey));
...@@ -514,6 +528,7 @@ void main() { ...@@ -514,6 +528,7 @@ void main() {
} }
await tester.pumpWidget(builder()); await tester.pumpWidget(builder());
await showKeyboard(tester);
String testValue = kThreeLines; String testValue = kThreeLines;
String cutValue = 'First line of stuff keeps going until abcdef ghijk. '; String cutValue = 'First line of stuff keeps going until abcdef ghijk. ';
...@@ -605,6 +620,7 @@ void main() { ...@@ -605,6 +620,7 @@ void main() {
} }
await tester.pumpWidget(builder()); await tester.pumpWidget(builder());
await showKeyboard(tester);
enterText(kFourLines); enterText(kFourLines);
await tester.idle(); await tester.idle();
...@@ -690,6 +706,7 @@ void main() { ...@@ -690,6 +706,7 @@ void main() {
} }
await tester.pumpWidget(builder()); await tester.pumpWidget(builder());
await showKeyboard(tester);
Future<Null> checkText(String testValue) async { Future<Null> checkText(String testValue) async {
enterText(testValue); enterText(testValue);
...@@ -722,6 +739,7 @@ void main() { ...@@ -722,6 +739,7 @@ void main() {
} }
await tester.pumpWidget(builder()); await tester.pumpWidget(builder());
await showKeyboard(tester);
Future<Null> checkText(String testValue) async { Future<Null> checkText(String testValue) async {
enterText(testValue); enterText(testValue);
......
...@@ -163,6 +163,10 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker ...@@ -163,6 +163,10 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
/// microtasks, by calling [pump] with the same `duration` (if any). The /// microtasks, by calling [pump] with the same `duration` (if any). The
/// supplied [EnginePhase] is the final phase reached during the pump pass; if /// supplied [EnginePhase] is the final phase reached during the pump pass; if
/// not supplied, the whole pass is executed. /// not supplied, the whole pass is executed.
///
/// Subsequent calls to this is different from [pump] in that it forces a full
/// rebuild of the tree, even if [widget] is the same as the previous call.
/// [pump] will only rebuild the widgets that have changed.
Future<Null> pumpWidget(Widget widget, [ Future<Null> pumpWidget(Widget widget, [
Duration duration, Duration duration,
EnginePhase phase = EnginePhase.sendSemanticsTree EnginePhase phase = EnginePhase.sendSemanticsTree
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment