Respond to AndroidView focus events. (#33901)

When an AndroidView gains focus we invoke the(newly introduced)
'TextInput.setPlatformViewClient' text_input system channel method
which sets the platform view as the text input target.

When the AndroidView loses focus we send a clearFocus message to
platform views system channel(so the engine can clear the focus from
the platform view).

This PR is going to land before the engine implementation is rolled to
the framework, we swallow MissingPluginException for the newly
introduced method channel methods so this is a no-op before the engine
is ready(after the engine is rolled with the corresponding change I'll
remove the logic to swallow the exceptions).

The engine counterpart is in: flutter/engine#9203
This commit is contained in:
Amir Hardon 2019-06-06 14:49:31 -07:00 committed by GitHub
parent fb8df82c06
commit 7d27550f6f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 154 additions and 0 deletions

View File

@ -583,6 +583,14 @@ class AndroidViewController {
});
}
/// Clears the focus from the Android View if it is focused.
Future<void> clearFocus() {
if (_state != _AndroidViewState.created) {
return null;
}
return SystemChannels.platform_views.invokeMethod<void>('clearFocus', id);
}
static int _getAndroidDirection(TextDirection direction) {
assert(direction != null);
switch (direction) {

View File

@ -306,6 +306,7 @@ class _AndroidViewState extends State<AndroidView> {
Widget build(BuildContext context) {
return Focus(
focusNode: _focusNode,
onFocusChange: _onFocusChange,
child: _AndroidPlatformView(
controller: _controller,
hitTestBehavior: widget.hitTestBehavior,
@ -384,6 +385,40 @@ class _AndroidViewState extends State<AndroidView> {
_controller.addOnPlatformViewCreatedListener(widget.onPlatformViewCreated);
}
}
void _onFocusChange(bool isFocused) {
if (!_controller.isCreated) {
return;
}
if (!isFocused) {
_controller.clearFocus().catchError((dynamic e) {
if (e is MissingPluginException) {
// We land the framework part of Android platform views keyboard
// support before the engine part. There will be a commit range where
// clearFocus isn't implemented in the engine. When that happens we
// just swallow the error here. Once the engine part is rolled to the
// framework I'll remove this.
// TODO(amirh): remove this once the engine's clearFocus is rolled.
return;
}
});
return;
}
SystemChannels.textInput.invokeMethod<void>(
'TextInput.setPlatformViewClient',
_id,
).catchError((dynamic e) {
if (e is MissingPluginException) {
// We land the framework part of Android platform views keyboard
// support before the engine part. There will be a commit range where
// setPlatformViewClient isn't implemented in the engine. When that
// happens we just swallow the error here. Once the engine part is
// rolled to the framework I'll remove this.
// TODO(amirh): remove this once the engine's clearFocus is rolled.
return;
}
});
}
}
class _UiKitViewState extends State<UiKitView> {

View File

@ -28,6 +28,8 @@ class FakeAndroidPlatformViewsController {
Completer<void> createCompleter;
int lastClearedFocusViewId;
void registerViewType(String viewType) {
_registeredViewTypes.add(viewType);
}
@ -50,6 +52,8 @@ class FakeAndroidPlatformViewsController {
return _touch(call);
case 'setDirection':
return _setDirection(call);
case 'clearFocus':
return _clearFocus(call);
}
return Future<dynamic>.sync(() => null);
}
@ -154,6 +158,19 @@ class FakeAndroidPlatformViewsController {
return Future<dynamic>.sync(() => null);
}
Future<dynamic> _clearFocus(MethodCall call) {
final int id = call.arguments;
if (!_views.containsKey(id))
throw PlatformException(
code: 'error',
message: 'Trying to clear the focus on a platform view with unknown id: $id',
);
lastClearedFocusViewId = id;
return Future<dynamic>.sync(() => null);
}
}
class FakeIosPlatformViewsController {

View File

@ -908,6 +908,100 @@ void main() {
expect(containerFocusNode.hasFocus, isFalse);
expect(androidViewFocusNode.hasFocus, isTrue);
});
testWidgets('AndroidView sets a platform view text input client when focused', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
viewsController.registerViewType('webview');
viewsController.createCompleter = Completer<void>();
final GlobalKey containerKey = GlobalKey();
await tester.pumpWidget(
Center(
child: Column(
children: <Widget>[
const SizedBox(
width: 200.0,
height: 100.0,
child: AndroidView(viewType: 'webview', layoutDirection: TextDirection.ltr),
),
Focus(
debugLabel: 'container',
child: Container(key: containerKey),
),
],
),
),
);
viewsController.createCompleter.complete();
final Element containerElement = tester.element(find.byKey(containerKey));
final FocusNode containerFocusNode = Focus.of(containerElement);
containerFocusNode.requestFocus();
await tester.pump();
int lastPlatformViewTextClient;
SystemChannels.textInput.setMockMethodCallHandler((MethodCall call) {
if (call.method == 'TextInput.setPlatformViewClient') {
lastPlatformViewTextClient = call.arguments;
}
return null;
});
viewsController.invokeViewFocused(currentViewId + 1);
await tester.pump();
expect(lastPlatformViewTextClient, currentViewId + 1);
});
testWidgets('AndroidView clears platform focus when unfocused', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
viewsController.registerViewType('webview');
viewsController.createCompleter = Completer<void>();
final GlobalKey containerKey = GlobalKey();
await tester.pumpWidget(
Center(
child: Column(
children: <Widget>[
const SizedBox(
width: 200.0,
height: 100.0,
child: AndroidView(viewType: 'webview', layoutDirection: TextDirection.ltr),
),
Focus(
debugLabel: 'container',
child: Container(key: containerKey),
),
],
),
),
);
viewsController.createCompleter.complete();
final Element containerElement = tester.element(find.byKey(containerKey));
final FocusNode containerFocusNode = Focus.of(containerElement);
containerFocusNode.requestFocus();
await tester.pump();
viewsController.invokeViewFocused(currentViewId + 1);
await tester.pump();
viewsController.lastClearedFocusViewId = null;
containerFocusNode.requestFocus();
await tester.pump();
expect(viewsController.lastClearedFocusViewId, currentViewId + 1);
});
});
group('UiKitView', () {