Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
.atom/
.idea/
.vscode/
**/.fvm/

.packages
.pub/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,9 @@ flutter:
- assets/sample_video.mp4
- assets/www/index.html
- assets/www/styles/style.css


# FOR TESTING ONLY. DO NOT MERGE.
dependency_overrides:
webview_flutter_platform_interface:
path: ../../../webview_flutter/webview_flutter_platform_interface
6 changes: 6 additions & 0 deletions packages/webview_flutter/webview_flutter/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,9 @@ dev_dependencies:
sdk: flutter
mockito: ^5.3.2
plugin_platform_interface: ^2.1.3


# FOR TESTING ONLY. DO NOT MERGE.
dependency_overrides:
webview_flutter_platform_interface:
path: ../../webview_flutter/webview_flutter_platform_interface
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 3.3.1

* Added `setOnContentOffsetChanged` method to the `AndroidWebViewController`.

## 3.3.0

* Adds support to access native `WebView`.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.plugins.webviewflutter;

public interface ContentOffsetChangedListener {
void onContentOffsetChange(int left, int top, int oldLeft, int oldTop);
}
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,8 @@ void removeJavaScriptChannel(

void setBackgroundColor(@NonNull Long instanceId, @NonNull Long color);

void enableContentOffsetChangedListener(@NonNull Long instanceId, @NonNull Boolean enabled);

/** The codec used by WebViewHostApi. */
static MessageCodec<Object> getCodec() {
return WebViewHostApiCodec.INSTANCE;
Expand Down Expand Up @@ -1558,6 +1560,40 @@ public void error(Throwable error) {
channel.setMessageHandler(null);
}
}
{
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger,
"dev.flutter.pigeon.WebViewHostApi.enableContentOffsetChangedListener",
getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList wrapped = new ArrayList<>();
try {
ArrayList<Object> args = (ArrayList<Object>) message;
assert args != null;
Number instanceIdArg = (Number) args.get(0);
if (instanceIdArg == null) {
throw new NullPointerException("instanceIdArg unexpectedly null.");
}
Boolean enabledArg = (Boolean) args.get(1);
if (enabledArg == null) {
throw new NullPointerException("enabledArg unexpectedly null.");
}
api.enableContentOffsetChangedListener(
(instanceIdArg == null) ? null : instanceIdArg.longValue(), enabledArg);
wrapped.add(0, null);
} catch (Error | RuntimeException exception) {
ArrayList<Object> wrappedError = wrapError(exception);
wrapped = wrappedError;
}
reply.reply(wrapped);
});
} else {
channel.setMessageHandler(null);
}
}
}
}
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
Expand Down Expand Up @@ -2438,6 +2474,41 @@ public void onDownloadStart(
});
}
}
/** Generated class from Pigeon that represents Flutter messages that can be called from Java. */
public static class WebViewFlutterApi {
private final BinaryMessenger binaryMessenger;

public WebViewFlutterApi(BinaryMessenger argBinaryMessenger) {
this.binaryMessenger = argBinaryMessenger;
}

public interface Reply<T> {
void reply(T reply);
}
/** The codec used by WebViewFlutterApi. */
static MessageCodec<Object> getCodec() {
return new StandardMessageCodec();
}

public void onScrollPosChange(
@NonNull Long webViewInstanceIdArg,
@NonNull Long xArg,
@NonNull Long yArg,
@NonNull Long oldXArg,
@NonNull Long oldYArg,
Reply<Void> callback) {
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger,
"dev.flutter.pigeon.WebViewFlutterApi.onScrollPosChange",
getCodec());
channel.send(
new ArrayList<Object>(Arrays.asList(webViewInstanceIdArg, xArg, yArg, oldXArg, oldYArg)),
channelReply -> {
callback.reply(null);
});
}
}
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
public interface WebChromeClientHostApi {
void create(@NonNull Long instanceId);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.plugins.webviewflutter;

import androidx.annotation.Nullable;
/** Define extending APIs for {@link android.webkit.WebView} */
public interface WebViewExtendedApi {
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of adding a new method to WebView, you should add a nonScrollChanged callback to the Dart WebView. The same way WebViewClient adds a callback: https://github.com/flutter/plugins/blob/main/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart#L759

I would also make the callback include the WebView for convenience:

final void Function(WebView webView, int left, int top, int oldLeft, int oldTop)? onScrollChanged;

Copy link
Author

Choose a reason for hiding this comment

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

Do you mean WebViewExtendedApi is not necessarily needed? I did read how the WebViewClient and attempted to do the same but couldn't find another way to set a scroll listener to a WebView without defining the WebViewExtendedApi .

The difference between setting a ScrollListener and a WebViewClient is that the webkit.WebView expose the method setWebViewClient for public use regardless of the Android required API level, this is unlike setOnScrollChangeListener since this is the method required min API level 23. The workaround here is to override the methodonScrollChanged of the WebView and get the offset changed event from there.

Do you have another approach for this?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yea you can start by adding the onScrollChanged parameter to WebView:

class WebView {
  WebView({this.useHybridComposition = false, this.onScrollChanged}) : super.detached() {
    api.createFromInstance(this);
  }

  final void Function(WebView webView, int left, int top, int oldLeft, int oldTop)? onScrollChanged;
}

Then will need to establish a Flutter API for WebView in the pigeons/android_webview.dart file:

@FlutterApi()
abstract class WebViewFlutterApi {
  void onScrollChanged(int identifier, ...);
}

Then you can run the generator and implement the Flutter API that is generated. The implementation goes in lib/src/android_webview_api_impl.dart.

Then in Java you'll need to override the onScrollChanged method in the WebViewPlatformView class found in WebViewHostApiImpl.java class. You can then instantiate a WebViewFlutterApi and call the WebViewFlutterApi.onScrollChanged method in this overridden method.

I may have missed a few things in this explanation, so definitely feel free to ask me for more clarification. The plugin is still suffering from tech debt, so adding new features still requires more work than they will in the future. I can also contribute code to this PR if needed.

Copy link
Author

Choose a reason for hiding this comment

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

Thank you for the feedback. I did as you said but even though it helped to make things simple I still end up needing to retain WebViewExtendedApi .

void setContentOffsetChangedListener(
@Nullable ContentOffsetChangedListener contentOffsetChangedListener);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.plugins.webviewflutter;

import android.webkit.WebView;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebViewFlutterApi;

/**
* Flutter Api implementation for {@link ContentOffsetChangedListener}.
*
* <p>Passes arguments of callbacks methods from a {@link ContentOffsetChangedListener} to Dart.
*/
public class WebViewFlutterApiImpl extends WebViewFlutterApi {
private final InstanceManager instanceManager;

/**
* Creates a Flutter api that sends messages to Dart.
*
* @param binaryMessenger handles sending messages to Dart
* @param instanceManager maintains instances stored to communicate with Dart objects
*/
public WebViewFlutterApiImpl(BinaryMessenger binaryMessenger, InstanceManager instanceManager) {
super(binaryMessenger);
this.instanceManager = instanceManager;
}

/** Passes arguments from {@link ContentOffsetChangedListener#onContentOffsetChange} to Dart. */
public void onScrollPosChange(
WebView webView, long x, long y, long oldX, long oldY, Reply<Void> callback) {
final Long webViewIdentifier = instanceManager.getIdentifierForStrongReference(webView);
if (webViewIdentifier == null) {
throw new IllegalStateException("Could not find identifier for WebView.");
}
onScrollPosChange(webViewIdentifier, x, y, oldX, oldY, callback);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ private void setUp(
instanceManager,
binaryMessenger,
new WebViewHostApiImpl.WebViewProxy(),
new WebViewFlutterApiImpl(binaryMessenger, instanceManager),
context,
containerView);
javaScriptChannelHostApi =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import androidx.annotation.Nullable;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.platform.PlatformView;
import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebViewFlutterApi;
import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebViewHostApi;
import java.util.Map;
import java.util.Objects;
Expand All @@ -31,6 +32,7 @@ public class WebViewHostApiImpl implements WebViewHostApi {
// Only used with WebView using virtual displays.
@Nullable private final View containerView;
private final BinaryMessenger binaryMessenger;
private final WebViewFlutterApi webViewFlutterApi;

private Context context;

Expand Down Expand Up @@ -77,9 +79,11 @@ public void setWebContentsDebuggingEnabled(boolean enabled) {
}

/** Implementation of {@link WebView} that can be used as a Flutter {@link PlatformView}s. */
public static class WebViewPlatformView extends WebView implements PlatformView {
public static class WebViewPlatformView extends WebView
implements PlatformView, WebViewExtendedApi {
private WebViewClient currentWebViewClient;
private WebChromeClientHostApiImpl.SecureWebChromeClient currentWebChromeClient;
private @Nullable ContentOffsetChangedListener contentOffsetChangedListener;

/**
* Creates a {@link WebViewPlatformView}.
Expand Down Expand Up @@ -129,6 +133,20 @@ public void setWebChromeClient(WebChromeClient client) {
public WebChromeClient getWebChromeClient() {
return currentWebChromeClient;
}

@Override
protected void onScrollChanged(int l, int t, int oldL, int oldT) {
super.onScrollChanged(l, t, oldL, oldT);
if (contentOffsetChangedListener != null) {
contentOffsetChangedListener.onContentOffsetChange(l, t, oldL, oldT);
}
}

@Override
public void setContentOffsetChangedListener(
ContentOffsetChangedListener contentOffsetChangedListener) {
this.contentOffsetChangedListener = contentOffsetChangedListener;
}
}

/**
Expand All @@ -137,9 +155,10 @@ public WebChromeClient getWebChromeClient() {
*/
@SuppressLint("ViewConstructor")
public static class InputAwareWebViewPlatformView extends InputAwareWebView
implements PlatformView {
implements PlatformView, WebViewExtendedApi {
private WebViewClient currentWebViewClient;
private WebChromeClientHostApiImpl.SecureWebChromeClient currentWebChromeClient;
private @Nullable ContentOffsetChangedListener contentOffsetChangedListener;

/**
* Creates a {@link InputAwareWebViewPlatformView}.
Expand Down Expand Up @@ -206,6 +225,20 @@ public void setWebChromeClient(WebChromeClient client) {
currentWebChromeClient = (WebChromeClientHostApiImpl.SecureWebChromeClient) client;
currentWebChromeClient.setWebViewClient(currentWebViewClient);
}

@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (contentOffsetChangedListener != null) {
contentOffsetChangedListener.onContentOffsetChange(l, t, oldl, oldt);
}
}

@Override
public void setContentOffsetChangedListener(
ContentOffsetChangedListener contentOffsetChangedListener) {
this.contentOffsetChangedListener = contentOffsetChangedListener;
}
}

/**
Expand All @@ -221,11 +254,13 @@ public WebViewHostApiImpl(
InstanceManager instanceManager,
BinaryMessenger binaryMessenger,
WebViewProxy webViewProxy,
WebViewFlutterApi webViewFlutterApi,
Context context,
@Nullable View containerView) {
this.instanceManager = instanceManager;
this.binaryMessenger = binaryMessenger;
this.webViewProxy = webViewProxy;
this.webViewFlutterApi = webViewFlutterApi;
this.context = context;
this.containerView = containerView;
}
Expand Down Expand Up @@ -420,6 +455,30 @@ public void setBackgroundColor(Long instanceId, Long color) {
webView.setBackgroundColor(color.intValue());
}

@Override
public void enableContentOffsetChangedListener(
@NonNull Long instanceId, @NonNull Boolean enabled) {
final WebView webView = (WebView) instanceManager.getInstance(instanceId);
if (webView instanceof WebViewExtendedApi) {
if (enabled) {
((WebViewExtendedApi) webView)
.setContentOffsetChangedListener(
(left, top, oldLeft, oldTop) -> {
webViewFlutterApi.onScrollPosChange(
instanceId,
(long) left,
(long) top,
(long) oldLeft,
(long) oldTop,
reply -> {});
});

} else {
((WebViewExtendedApi) webView).setContentOffsetChangedListener(null);
}
}
}

/** Maintains instances used to communicate with the corresponding WebView Dart object. */
public InstanceManager getInstanceManager() {
return instanceManager;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
Expand Down Expand Up @@ -37,6 +39,8 @@ public class WebViewTest {

@Mock WebViewHostApiImpl.WebViewProxy mockWebViewProxy;

@Mock public GeneratedAndroidWebView.WebViewFlutterApi mockWebViewFlutterApi;

@Mock Context mockContext;

@Mock BinaryMessenger mockBinaryMessenger;
Expand All @@ -52,7 +56,12 @@ public void setUp() {
.thenReturn(mockWebView);
testHostApiImpl =
new WebViewHostApiImpl(
testInstanceManager, mockBinaryMessenger, mockWebViewProxy, mockContext, null);
testInstanceManager,
mockBinaryMessenger,
mockWebViewProxy,
mockWebViewFlutterApi,
mockContext,
null);
testHostApiImpl.create(0L, true);
}

Expand Down Expand Up @@ -314,4 +323,21 @@ public void destroy() {

assertTrue(destroyCalled[0]);
}

@Test
public void disableContentOffsetChangedListener() {
testHostApiImpl.enableContentOffsetChangedListener(0L, false);
verify(mockWebView).setContentOffsetChangedListener(null);
}

@Test
public void enableContentOffsetChangedListener() {
final ArgumentCaptor<ContentOffsetChangedListener> modeCaptor =
ArgumentCaptor.forClass(ContentOffsetChangedListener.class);
testHostApiImpl.enableContentOffsetChangedListener(0L, true);
verify(mockWebView).setContentOffsetChangedListener(modeCaptor.capture());
assertNotNull(modeCaptor.getValue());
modeCaptor.getValue().onContentOffsetChange(0, 1, 2, 3);
verify(mockWebViewFlutterApi).onScrollPosChange(eq(0L), eq(0L), eq(1L), eq(2L), eq(3L), any());
}
}
Loading