Skip to content

Commit 26889ce

Browse files
[webview_flutter_android][webview_flutter_wkwebview] Fixes bug where PlatformWebViewWidget doesn't rebuild when the controller changes (#4533)
In a scenario where a `WebViewWidget` was updated with a new `WebViewController`, the native `WebView` from the new controller would not be shown. e.g. ```dart class WebViewExample extends StatefulWidget { const WebViewExample({super.key}); @OverRide State<WebViewExample> createState() => _WebViewExampleState(); } class _WebViewExampleState extends State<WebViewExample> { late WebViewController controller; @OverRide void initState() { super.initState(); controller = WebViewController() ..loadRequest(Uri.parse('https://flutter.dev')); } @OverRide Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Flutter Simple Example')), body: WebViewWidget(controller: controller), floatingActionButton: FloatingActionButton( onPressed: () { setState(() { controller = WebViewController() ..loadRequest(Uri.parse('https://google.com')); }); }, child: const Icon(Icons.add), ), ); } } ``` From testing, the `WebViewWidget` would continue showing the original `WebViewController` and eventually freeze the PlatformView preventing button presses. This is because the PlatformView widget only creates the native PlatformView once and doesn't recreate it when the creation parameters change (as expected). This adds a default `key` to the `PlatformWebViewWidget` that is used to indicate when the underlying widget needs to be removed and recreated. See https://api.flutter.dev/flutter/widgets/Widget/key.html
1 parent 2481d92 commit 26889ce

File tree

8 files changed

+197
-35
lines changed

8 files changed

+197
-35
lines changed

packages/webview_flutter/webview_flutter_android/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 3.9.2
2+
3+
* Fixes bug where `PlatformWebViewWidget` doesn't rebuild when the controller or PlatformView
4+
implementation flag changes.
5+
16
## 3.9.1
27

38
* Adjusts SDK checks for better testability.

packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -744,6 +744,25 @@ class AndroidWebViewWidgetCreationParams
744744
///
745745
/// Defaults to false.
746746
final bool displayWithHybridComposition;
747+
748+
@override
749+
int get hashCode => Object.hash(
750+
controller,
751+
layoutDirection,
752+
displayWithHybridComposition,
753+
platformViewsServiceProxy,
754+
instanceManager,
755+
);
756+
757+
@override
758+
bool operator ==(Object other) {
759+
return other is AndroidWebViewWidgetCreationParams &&
760+
controller == other.controller &&
761+
layoutDirection == other.layoutDirection &&
762+
displayWithHybridComposition == other.displayWithHybridComposition &&
763+
platformViewsServiceProxy == other.platformViewsServiceProxy &&
764+
instanceManager == other.instanceManager;
765+
}
747766
}
748767

749768
/// An implementation of [PlatformWebViewWidget] with the Android WebView API.
@@ -763,7 +782,9 @@ class AndroidWebViewWidget extends PlatformWebViewWidget {
763782
@override
764783
Widget build(BuildContext context) {
765784
return PlatformViewLink(
766-
key: _androidParams.key,
785+
// Setting a default key using `params` ensures the `PlatformViewLink`
786+
// recreates the PlatformView when changes are made.
787+
key: _androidParams.key ?? ObjectKey(params),
767788
viewType: 'plugins.flutter.io/webview',
768789
surfaceFactory: (
769790
BuildContext context,

packages/webview_flutter/webview_flutter_android/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: webview_flutter_android
22
description: A Flutter plugin that provides a WebView widget on Android.
33
repository: https://github.com/flutter/packages/tree/main/packages/webview_flutter/webview_flutter_android
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22
5-
version: 3.9.1
5+
version: 3.9.2
66

77
environment:
88
sdk: ">=2.18.0 <4.0.0"

packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1216,5 +1216,68 @@ void main() {
12161216
),
12171217
);
12181218
});
1219+
1220+
testWidgets('PlatformView is recreated when the controller changes',
1221+
(WidgetTester tester) async {
1222+
final MockPlatformViewsServiceProxy mockPlatformViewsService =
1223+
MockPlatformViewsServiceProxy();
1224+
1225+
when(
1226+
mockPlatformViewsService.initSurfaceAndroidView(
1227+
id: anyNamed('id'),
1228+
viewType: anyNamed('viewType'),
1229+
layoutDirection: anyNamed('layoutDirection'),
1230+
creationParams: anyNamed('creationParams'),
1231+
creationParamsCodec: anyNamed('creationParamsCodec'),
1232+
onFocus: anyNamed('onFocus'),
1233+
),
1234+
).thenReturn(MockSurfaceAndroidViewController());
1235+
1236+
await tester.pumpWidget(Builder(
1237+
builder: (BuildContext context) {
1238+
return AndroidWebViewWidget(
1239+
AndroidWebViewWidgetCreationParams(
1240+
controller: createControllerWithMocks(),
1241+
platformViewsServiceProxy: mockPlatformViewsService,
1242+
),
1243+
).build(context);
1244+
},
1245+
));
1246+
await tester.pumpAndSettle();
1247+
1248+
verify(
1249+
mockPlatformViewsService.initSurfaceAndroidView(
1250+
id: anyNamed('id'),
1251+
viewType: anyNamed('viewType'),
1252+
layoutDirection: anyNamed('layoutDirection'),
1253+
creationParams: anyNamed('creationParams'),
1254+
creationParamsCodec: anyNamed('creationParamsCodec'),
1255+
onFocus: anyNamed('onFocus'),
1256+
),
1257+
);
1258+
1259+
await tester.pumpWidget(Builder(
1260+
builder: (BuildContext context) {
1261+
return AndroidWebViewWidget(
1262+
AndroidWebViewWidgetCreationParams(
1263+
controller: createControllerWithMocks(),
1264+
platformViewsServiceProxy: mockPlatformViewsService,
1265+
),
1266+
).build(context);
1267+
},
1268+
));
1269+
await tester.pumpAndSettle();
1270+
1271+
verify(
1272+
mockPlatformViewsService.initSurfaceAndroidView(
1273+
id: anyNamed('id'),
1274+
viewType: anyNamed('viewType'),
1275+
layoutDirection: anyNamed('layoutDirection'),
1276+
creationParams: anyNamed('creationParams'),
1277+
creationParamsCodec: anyNamed('creationParamsCodec'),
1278+
onFocus: anyNamed('onFocus'),
1279+
),
1280+
);
1281+
});
12191282
});
12201283
}

packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 3.7.2
2+
3+
* Fixes bug where `PlatformWebViewWidget` doesn't rebuild when the controller changes.
4+
15
## 3.7.1
26

37
* Updates pigeon version to `10.1.4`.

packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_webview_controller.dart

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,21 @@ class WebKitWebViewWidgetCreationParams
632632
// Maintains instances used to communicate with the native objects they
633633
// represent.
634634
final InstanceManager _instanceManager;
635+
636+
@override
637+
int get hashCode => Object.hash(
638+
controller,
639+
layoutDirection,
640+
_instanceManager,
641+
);
642+
643+
@override
644+
bool operator ==(Object other) {
645+
return other is WebKitWebViewWidgetCreationParams &&
646+
controller == other.controller &&
647+
layoutDirection == other.layoutDirection &&
648+
_instanceManager == other._instanceManager;
649+
}
635650
}
636651

637652
/// An implementation of [PlatformWebViewWidget] with the WebKit api.
@@ -651,7 +666,9 @@ class WebKitWebViewWidget extends PlatformWebViewWidget {
651666
@override
652667
Widget build(BuildContext context) {
653668
return UiKitView(
654-
key: _webKitParams.key,
669+
// Setting a default key using `params` ensures the `UIKitView` recreates
670+
// the PlatformView when changes are made.
671+
key: _webKitParams.key ?? ObjectKey(params),
655672
viewType: 'plugins.flutter.io/webview',
656673
onPlatformViewCreated: (_) {},
657674
layoutDirection: params.layoutDirection,

packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: webview_flutter_wkwebview
22
description: A Flutter plugin that provides a WebView widget based on Apple's WKWebView control.
33
repository: https://github.com/flutter/packages/tree/main/packages/webview_flutter/webview_flutter_wkwebview
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22
5-
version: 3.7.1
5+
version: 3.7.2
66

77
environment:
88
sdk: ">=2.18.0 <4.0.0"

packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_widget_test.dart

Lines changed: 83 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -24,37 +24,8 @@ void main() {
2424
onWeakReferenceRemoved: (_) {},
2525
);
2626

27-
final WebKitWebViewController controller = WebKitWebViewController(
28-
WebKitWebViewControllerCreationParams(
29-
webKitProxy: WebKitProxy(createWebView: (
30-
WKWebViewConfiguration configuration, {
31-
void Function(
32-
String keyPath,
33-
NSObject object,
34-
Map<NSKeyValueChangeKey, Object?> change,
35-
)? observeValue,
36-
InstanceManager? instanceManager,
37-
}) {
38-
final WKWebView webView = WKWebView.detached(
39-
instanceManager: testInstanceManager,
40-
);
41-
testInstanceManager.addDartCreatedInstance(webView);
42-
return webView;
43-
}, createWebViewConfiguration: ({InstanceManager? instanceManager}) {
44-
return MockWKWebViewConfiguration();
45-
}, createUIDelegate: ({
46-
dynamic onCreateWebView,
47-
dynamic requestMediaCapturePermission,
48-
InstanceManager? instanceManager,
49-
}) {
50-
final MockWKUIDelegate mockWKUIDelegate = MockWKUIDelegate();
51-
when(mockWKUIDelegate.copy()).thenReturn(MockWKUIDelegate());
52-
53-
testInstanceManager.addDartCreatedInstance(mockWKUIDelegate);
54-
return mockWKUIDelegate;
55-
}),
56-
),
57-
);
27+
final WebKitWebViewController controller =
28+
createTestWebViewController(testInstanceManager);
5829

5930
final WebKitWebViewWidget widget = WebKitWebViewWidget(
6031
WebKitWebViewWidgetCreationParams(
@@ -71,5 +42,86 @@ void main() {
7142
expect(find.byType(UiKitView), findsOneWidget);
7243
expect(find.byKey(const Key('keyValue')), findsOneWidget);
7344
});
45+
46+
testWidgets('Key of the PlatformView changes when the controller changes',
47+
(WidgetTester tester) async {
48+
final InstanceManager testInstanceManager = InstanceManager(
49+
onWeakReferenceRemoved: (_) {},
50+
);
51+
52+
// Pump WebViewWidget with first controller.
53+
final WebKitWebViewController controller1 =
54+
createTestWebViewController(testInstanceManager);
55+
final WebKitWebViewWidget webViewWidget = WebKitWebViewWidget(
56+
WebKitWebViewWidgetCreationParams(
57+
controller: controller1,
58+
instanceManager: testInstanceManager,
59+
),
60+
);
61+
62+
await tester.pumpWidget(
63+
Builder(
64+
builder: (BuildContext context) => webViewWidget.build(context),
65+
),
66+
);
67+
await tester.pumpAndSettle();
68+
69+
expect(find.byKey(ObjectKey(webViewWidget.params)), findsOneWidget);
70+
71+
// Pump WebViewWidget with second controller.
72+
final WebKitWebViewController controller2 =
73+
createTestWebViewController(testInstanceManager);
74+
final WebKitWebViewWidget webViewWidget2 = WebKitWebViewWidget(
75+
WebKitWebViewWidgetCreationParams(
76+
controller: controller2,
77+
instanceManager: testInstanceManager,
78+
),
79+
);
80+
81+
await tester.pumpWidget(
82+
Builder(
83+
builder: (BuildContext context) => webViewWidget2.build(context),
84+
),
85+
);
86+
await tester.pumpAndSettle();
87+
88+
expect(find.byKey(ObjectKey(webViewWidget2.params)), findsOneWidget);
89+
});
7490
});
7591
}
92+
93+
WebKitWebViewController createTestWebViewController(
94+
InstanceManager testInstanceManager,
95+
) {
96+
return WebKitWebViewController(
97+
WebKitWebViewControllerCreationParams(
98+
webKitProxy: WebKitProxy(createWebView: (
99+
WKWebViewConfiguration configuration, {
100+
void Function(
101+
String keyPath,
102+
NSObject object,
103+
Map<NSKeyValueChangeKey, Object?> change,
104+
)? observeValue,
105+
InstanceManager? instanceManager,
106+
}) {
107+
final WKWebView webView = WKWebView.detached(
108+
instanceManager: testInstanceManager,
109+
);
110+
testInstanceManager.addDartCreatedInstance(webView);
111+
return webView;
112+
}, createWebViewConfiguration: ({InstanceManager? instanceManager}) {
113+
return MockWKWebViewConfiguration();
114+
}, createUIDelegate: ({
115+
dynamic onCreateWebView,
116+
dynamic requestMediaCapturePermission,
117+
InstanceManager? instanceManager,
118+
}) {
119+
final MockWKUIDelegate mockWKUIDelegate = MockWKUIDelegate();
120+
when(mockWKUIDelegate.copy()).thenReturn(MockWKUIDelegate());
121+
122+
testInstanceManager.addDartCreatedInstance(mockWKUIDelegate);
123+
return mockWKUIDelegate;
124+
}),
125+
),
126+
);
127+
}

0 commit comments

Comments
 (0)