Skip to content

Commit 0c2473f

Browse files
authored
[webview_flutter_android][webview_flutter_wkwebview] Adds support for setOnScrollPositionChange for webview_flutter platform implementations (#5664)
Adds iOS and Android implementation for content offset listener This PR is part of a series of PRs that aim to close flutter/flutter#31027. The PR that contains all changes can be found at #3444.
1 parent e4ea6bf commit 0c2473f

File tree

49 files changed

+2038
-784
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+2038
-784
lines changed

packages/webview_flutter/webview_flutter_android/AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,5 @@ Rahul Raj <64.rahulraj@gmail.com>
6767
Maurits van Beusekom <maurits@baseflow.com>
6868
Nick Bradshaw <nickalasb@gmail.com>
6969
Kai Yu <yk3372@gmail.com>
70+
The Vinh Luong <ltv.luongthevinh@gmail.com>
7071

packages/webview_flutter/webview_flutter_android/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 3.15.0
2+
3+
* Adds support for `setOnScrollPositionChange` method to the `AndroidWebViewController`.
4+
15
## 3.14.0
26

37
* Adds support to show JavaScript dialog. See `AndroidWebViewController.setOnJavaScriptAlertDialog`, `AndroidWebViewController.setOnJavaScriptConfirmDialog` and `AndroidWebViewController.setOnJavaScriptTextInputDialog`.

packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1758,6 +1758,24 @@ public void create(@NonNull Long identifierArg, @NonNull Reply<Void> callback) {
17581758
new ArrayList<Object>(Collections.singletonList(identifierArg)),
17591759
channelReply -> callback.reply(null));
17601760
}
1761+
1762+
public void onScrollChanged(
1763+
@NonNull Long webViewInstanceIdArg,
1764+
@NonNull Long leftArg,
1765+
@NonNull Long topArg,
1766+
@NonNull Long oldLeftArg,
1767+
@NonNull Long oldTopArg,
1768+
@NonNull Reply<Void> callback) {
1769+
BasicMessageChannel<Object> channel =
1770+
new BasicMessageChannel<>(
1771+
binaryMessenger,
1772+
"dev.flutter.pigeon.webview_flutter_android.WebViewFlutterApi.onScrollChanged",
1773+
getCodec());
1774+
channel.send(
1775+
new ArrayList<Object>(
1776+
Arrays.asList(webViewInstanceIdArg, leftArg, topArg, oldLeftArg, oldTopArg)),
1777+
channelReply -> callback.reply(null));
1778+
}
17611779
}
17621780
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
17631781
public interface WebSettingsHostApi {

packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterApiImpl.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import androidx.annotation.VisibleForTesting;
1010
import io.flutter.plugin.common.BinaryMessenger;
1111
import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebViewFlutterApi;
12+
import java.util.Objects;
1213

1314
/**
1415
* Flutter API implementation for `WebView`.
@@ -56,4 +57,20 @@ public void create(@NonNull WebView instance, @NonNull WebViewFlutterApi.Reply<V
5657
void setApi(@NonNull WebViewFlutterApi api) {
5758
this.api = api;
5859
}
60+
61+
public void onScrollChanged(
62+
@NonNull WebView instance,
63+
@NonNull Long left,
64+
@NonNull Long top,
65+
@NonNull Long oldLeft,
66+
@NonNull Long oldTop,
67+
@NonNull WebViewFlutterApi.Reply<Void> callback) {
68+
api.onScrollChanged(
69+
Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(instance)),
70+
left,
71+
top,
72+
oldLeft,
73+
oldTop,
74+
callback);
75+
}
5976
}

packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewHostApiImpl.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,13 @@ public WebChromeClient getWebChromeClient() {
180180
return currentWebChromeClient;
181181
}
182182

183+
@Override
184+
protected void onScrollChanged(int left, int top, int oldLeft, int oldTop) {
185+
super.onScrollChanged(left, top, oldLeft, oldTop);
186+
api.onScrollChanged(
187+
this, (long) left, (long) top, (long) oldLeft, (long) oldTop, reply -> {});
188+
}
189+
183190
/**
184191
* Flutter API used to send messages back to Dart.
185192
*

packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewTest.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,4 +358,25 @@ public void setImportantForAutofillForParentFlutterView() {
358358

359359
verify(mockFlutterView).setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_YES);
360360
}
361+
362+
@Test
363+
public void onScrollChanged() {
364+
final InstanceManager instanceManager = InstanceManager.create(identifier -> {});
365+
366+
final WebViewFlutterApiImpl flutterApiImpl =
367+
new WebViewFlutterApiImpl(mockBinaryMessenger, instanceManager);
368+
369+
final WebViewFlutterApi mockFlutterApi = mock(WebViewFlutterApi.class);
370+
flutterApiImpl.setApi(mockFlutterApi);
371+
flutterApiImpl.create(mockWebView, reply -> {});
372+
373+
flutterApiImpl.onScrollChanged(mockWebView, 0L, 1L, 2L, 3L, reply -> {});
374+
375+
final long instanceIdentifier =
376+
Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(mockWebView));
377+
verify(mockFlutterApi)
378+
.onScrollChanged(eq(instanceIdentifier), eq(0L), eq(1L), eq(2L), eq(3L), any());
379+
380+
instanceManager.stopFinalizationListener();
381+
}
361382
}

packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -758,7 +758,8 @@ Future<void> main() async {
758758
});
759759

760760
group('Programmatic Scroll', () {
761-
testWidgets('setAndGetScrollPosition', (WidgetTester tester) async {
761+
testWidgets('setAndGetAndListenScrollPosition',
762+
(WidgetTester tester) async {
762763
const String scrollTestPage = '''
763764
<!DOCTYPE html>
764765
<html>
@@ -784,6 +785,7 @@ Future<void> main() async {
784785
base64Encode(const Utf8Encoder().convert(scrollTestPage));
785786

786787
final Completer<void> pageLoaded = Completer<void>();
788+
ScrollPositionChange? recordedPosition;
787789
final PlatformWebViewController controller = PlatformWebViewController(
788790
const PlatformWebViewControllerCreationParams(),
789791
);
@@ -793,6 +795,10 @@ Future<void> main() async {
793795
);
794796
unawaited(delegate.setOnPageFinished((_) => pageLoaded.complete()));
795797
unawaited(controller.setPlatformNavigationDelegate(delegate));
798+
unawaited(controller.setOnScrollPositionChange(
799+
(ScrollPositionChange contentOffsetChange) {
800+
recordedPosition = contentOffsetChange;
801+
}));
796802

797803
await controller.loadRequest(
798804
LoadRequestParams(
@@ -824,17 +830,22 @@ Future<void> main() async {
824830
// time to settle.
825831
expect(scrollPos.dx, isNot(X_SCROLL));
826832
expect(scrollPos.dy, isNot(Y_SCROLL));
833+
expect(recordedPosition, null);
827834

828835
await controller.scrollTo(X_SCROLL, Y_SCROLL);
829836
scrollPos = await controller.getScrollPosition();
830837
expect(scrollPos.dx, X_SCROLL);
831838
expect(scrollPos.dy, Y_SCROLL);
839+
expect(recordedPosition?.x, X_SCROLL);
840+
expect(recordedPosition?.y, Y_SCROLL);
832841

833842
// Check scrollBy() (on top of scrollTo())
834843
await controller.scrollBy(X_SCROLL, Y_SCROLL);
835844
scrollPos = await controller.getScrollPosition();
836845
expect(scrollPos.dx, X_SCROLL * 2);
837846
expect(scrollPos.dy, Y_SCROLL * 2);
847+
expect(recordedPosition?.x, X_SCROLL * 2);
848+
expect(recordedPosition?.y, Y_SCROLL * 2);
838849
});
839850
});
840851

packages/webview_flutter/webview_flutter_android/example/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ dependencies:
1717
# The example app is bundled with the plugin so we use a path dependency on
1818
# the parent directory to use the current plugin's version.
1919
path: ../
20-
webview_flutter_platform_interface: ^2.9.0
20+
webview_flutter_platform_interface: ^2.10.0
2121

2222
dev_dependencies:
2323
espresso: ^0.2.0

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ class AndroidWebViewProxy {
2525
});
2626

2727
/// Constructs a [android_webview.WebView].
28-
final android_webview.WebView Function() createAndroidWebView;
28+
final android_webview.WebView Function({
29+
void Function(int left, int top, int oldLeft, int oldTop)? onScrollChanged,
30+
}) createAndroidWebView;
2931

3032
/// Constructs a [android_webview.WebChromeClient].
3133
final android_webview.WebChromeClient Function({

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

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,11 +132,8 @@ class GeolocationPermissionsCallback extends JavaObject {
132132
/// When a [WebView] is no longer needed [release] must be called.
133133
class WebView extends View {
134134
/// Constructs a new WebView.
135-
///
136-
/// Due to changes in Flutter 3.0 the [useHybridComposition] doesn't have
137-
/// any effect and should not be exposed publicly. More info here:
138-
/// https://github.com/flutter/flutter/issues/108106
139135
WebView({
136+
this.onScrollChanged,
140137
@visibleForTesting super.binaryMessenger,
141138
@visibleForTesting super.instanceManager,
142139
}) : super.detached() {
@@ -149,6 +146,7 @@ class WebView extends View {
149146
/// create copies.
150147
@protected
151148
WebView.detached({
149+
this.onScrollChanged,
152150
super.binaryMessenger,
153151
super.instanceManager,
154152
}) : super.detached();
@@ -160,6 +158,18 @@ class WebView extends View {
160158
/// The [WebSettings] object used to control the settings for this WebView.
161159
late final WebSettings settings = WebSettings(this);
162160

161+
/// Called in response to an internal scroll in this view
162+
/// (i.e., the view scrolled its own contents).
163+
///
164+
/// This is typically as a result of [scrollBy] or [scrollTo]
165+
/// having been called.
166+
final void Function(
167+
int left,
168+
int top,
169+
int oldLeft,
170+
int oldTop,
171+
)? onScrollChanged;
172+
163173
/// Enables debugging of web contents (HTML / CSS / JavaScript) loaded into any WebViews of this application.
164174
///
165175
/// This flag can be enabled in order to facilitate debugging of web layouts
@@ -448,6 +458,7 @@ class WebView extends View {
448458
@override
449459
WebView copy() {
450460
return WebView.detached(
461+
onScrollChanged: onScrollChanged,
451462
binaryMessenger: _api.binaryMessenger,
452463
instanceManager: _api.instanceManager,
453464
);

0 commit comments

Comments
 (0)