Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

[webview_flutter] Support for loading progress tracking #2151

Merged
merged 39 commits into from
Feb 18, 2021
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
2375fa7
Added support for loading progress tracking (iOS & Android)
jeremie-movify Oct 4, 2019
704cc75
Updated changelog, bumped plugin version & fixed Dart analyzer
jeremie-movify Oct 4, 2019
90b47c1
Fixed formatting
jeremie-movify Oct 4, 2019
a795c90
Fixed formatting for FlutterWebView & FlutterWebViewClient
jeremie-movify Oct 4, 2019
a618831
One more formatting pass
jeremie-movify Oct 4, 2019
e201d3c
One more formatting pass
jeremie-movify Oct 4, 2019
1fe7ef4
Merge branch 'master' into progress-tracking
jeremie-movify Nov 4, 2019
03a6307
Merge branch 'master' into progress-tracking
jeremie-movify Nov 22, 2019
b1052b4
Merge branch 'master' into progress-tracking
jeremie-movify Jan 28, 2020
5cd61b2
Update FLTWKNavigationDelegate.h
jeremie-movify Jan 28, 2020
f153499
Update pubspec.yaml
jeremie-movify Jan 28, 2020
19d697e
Added missing null return
jeremie-movify Mar 2, 2020
9ceea3c
Merge branch 'master' into progress-tracking
jeremie-movify Mar 2, 2020
a5906b3
Merge branch 'master' into progress-tracking
jeremie-movify Mar 19, 2020
a357555
Merge branch 'flutter-origin-master' into progress-tracking
jeremie-movify Apr 6, 2020
eb809fc
Merge branch 'master' into progress-tracking
jeremie-movify Apr 10, 2020
636ad6f
Merge branch 'master' into progress-tracking
jeremie-movify Apr 28, 2020
677bad9
Trigger checks
jeremie-movify May 6, 2020
25af806
Merge branch 'master' into progress-tracking
jeremie-movify May 29, 2020
a020da2
Merge branch 'master' into progress-tracking
jeremie-movify Aug 27, 2020
07ab322
Improvements based on @cyanglaz's feedback
jeremie-movify Sep 10, 2020
f3de98b
Only observe newValues on webView's 'estimatedProgress' keyPath
jeremie-movify Sep 10, 2020
76e2d76
Fixed formatting
jeremie-movify Sep 11, 2020
a778bf3
Updated keyPath constant name to FLTWKEstimatedProgressKeyPath
jeremie-movify Sep 14, 2020
0090288
Fixed formatting
jeremie-movify Sep 14, 2020
a6f2b53
Merge branch 'flutter-origin-master' into progress-tracking
jeremie-movify Sep 22, 2020
a706d3e
Fixed formatting
jeremie-movify Sep 22, 2020
2897586
Merge branch 'flutter-origin-master' into progress-tracking
jeremie-movify Nov 16, 2020
24c77c2
Merge branch 'flutter-origin-master' into progress-tracking
jeremie-movify Jan 12, 2021
069af29
Fixed hasProgressTracking nullability
jeremie-movify Jan 12, 2021
6fa576d
Nullability fixes
jeremie-movify Jan 12, 2021
7d7c400
One more
jeremie-movify Jan 12, 2021
11e6ade
Fixed imports
jeremie-movify Jan 14, 2021
1747608
Merge branch 'flutter-origin-master' into progress-tracking
jeremie-movify Jan 14, 2021
16c7d33
Trigger checks
jeremie-movify Jan 15, 2021
73d09be
Imported androidx.annotation.NonNull to fix apk build on cirrus
jeremie-movify Jan 18, 2021
a9bcf56
Merge branch 'master' into progress-tracking
jeremie-movify Feb 16, 2021
d9438f1
Merge branch 'flutter-origin-master' into progress-tracking
jeremie-movify Feb 17, 2021
6cc00fc
PR remarks: Android: improved progress tracking setup & reused existi…
jeremie-movify Feb 17, 2021
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
4 changes: 4 additions & 0 deletions packages/webview_flutter/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2.0.0-nullsafety.4

* Added support for progress tracking.

## 2.0.0-nullsafety.3

* Fix `onWebResourceError` on iOS.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,16 @@ private void applySettings(Map<String, Object> settings) {
webView.setWebContentsDebuggingEnabled(debuggingEnabled);
}
break;
case "hasProgressTracking":
final boolean progressTrackingEnabled = (boolean) settings.get(key);
if (progressTrackingEnabled) {
webView.setWebChromeClient(
new WebChromeClient() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we refactor the WebChromeClient out to somewhere else? Maybe an instance var? We likely will add more to the WebChromeClient in the future and it would be cleaner to have it somewhere stand alone.

Copy link
Contributor

Choose a reason for hiding this comment

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

We have another PR which is going to add a WebChromeClient to the webview: #2991. There will be conflicts once the other PR lands.
Maybe let's wait for the other PR to land and rebase?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Absolutely

Copy link
Contributor

Choose a reason for hiding this comment

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

Can we refactor this code into FlutterWebChromeClient instead of creating a new WebChromeClient

public void onProgressChanged(WebView view, int progress) {
flutterWebViewClient.onLoadingProgress(progress);
}
});
}
case "gestureNavigationEnabled":
break;
case "userAgent":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class FlutterWebViewClient {
private static final String TAG = "FlutterWebViewClient";
private final MethodChannel methodChannel;
private boolean hasNavigationDelegate;
private boolean hasProgressTracking;
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this used anywhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch, this flag wasn't used properly. I've updated the PR so that progress changes are sent to the method channel only if hasProgressTracking is true.


FlutterWebViewClient(MethodChannel methodChannel) {
this.methodChannel = methodChannel;
Expand Down Expand Up @@ -125,6 +126,12 @@ private void onPageFinished(WebView view, String url) {
methodChannel.invokeMethod("onPageFinished", args);
}

void onLoadingProgress(int progress) {
Map<String, Object> args = new HashMap<>();
args.put("progress", progress);
methodChannel.invokeMethod("onProgress", args);
}

private void onWebResourceError(
final int errorCode, final String description, final String failingUrl) {
final Map<String, Object> args = new HashMap<>();
Expand Down
5 changes: 5 additions & 0 deletions packages/webview_flutter/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ class _WebViewExampleState extends State<WebViewExample> {
onWebViewCreated: (WebViewController webViewController) {
_controller.complete(webViewController);
},
onProgress: (int progress) {
print("WebView is loading (progress : $progress%)");
},
// TODO(iskakaushik): Remove this when collection literals makes it to stable.
// ignore: prefer_collection_literals
Copy link
Contributor

Choose a reason for hiding this comment

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

do we still need this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Indeed we don't; I've removed these lines.

javascriptChannels: <JavascriptChannel>{
_toasterJavascriptChannel(context),
},
Expand Down
19 changes: 19 additions & 0 deletions packages/webview_flutter/ios/Classes/FLTWKProgressionDelegate.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import <Flutter/Flutter.h>
#import <Foundation/Foundation.h>
#import <WebKit/WebKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface FLTWKProgressionDelegate : NSObject

- (instancetype)initWithWebView:(WKWebView *)webView channel:(FlutterMethodChannel *)channel;

- (void)stopObservingProgress:(WKWebView *)webView;

@end

NS_ASSUME_NONNULL_END
42 changes: 42 additions & 0 deletions packages/webview_flutter/ios/Classes/FLTWKProgressionDelegate.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "FLTWKProgressionDelegate.h"

NSString *const FLTWKEstimatedProgressKeyPath = @"estimatedProgress";

@implementation FLTWKProgressionDelegate {
FlutterMethodChannel *_methodChannel;
}

- (instancetype)initWithWebView:(WKWebView *)webView channel:(FlutterMethodChannel *)channel {
self = [super init];
if (self) {
_methodChannel = channel;
[webView addObserver:self
forKeyPath:FLTWKEstimatedProgressKeyPath
options:NSKeyValueObservingOptionNew
context:nil];
}
return self;
}

- (void)stopObservingProgress:(WKWebView *)webView {
[webView removeObserver:self forKeyPath:FLTWKEstimatedProgressKeyPath];
}

- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey, id> *)change
context:(void *)context {
if ([keyPath isEqualToString:FLTWKEstimatedProgressKeyPath]) {
NSNumber *newValue =
change[NSKeyValueChangeNewKey] ?: 0; // newValue is anywhere between 0.0 and 1.0
int newValueAsInt = [newValue floatValue] * 100; // Anywhere between 0 and 100
[_methodChannel invokeMethod:@"onProgress"
arguments:@{@"progress" : [NSNumber numberWithInt:newValueAsInt]}];
}
}

@end
15 changes: 15 additions & 0 deletions packages/webview_flutter/ios/Classes/FlutterWebView.m
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#import "FlutterWebView.h"
#import "FLTWKNavigationDelegate.h"
#import "FLTWKProgressionDelegate.h"
#import "JavaScriptChannelHandler.h"

@implementation FLTWebViewFactory {
Expand Down Expand Up @@ -64,6 +65,7 @@ @implementation FLTWebViewController {
// The set of registered JavaScript channel names.
NSMutableSet* _javaScriptChannelNames;
FLTWKNavigationDelegate* _navigationDelegate;
FLTWKProgressionDelegate* _progressionDelegate;
}

- (instancetype)initWithFrame:(CGRect)frame
Expand Down Expand Up @@ -119,6 +121,12 @@ - (instancetype)initWithFrame:(CGRect)frame
return self;
}

- (void)dealloc {
if (_progressionDelegate != nil) {
[_progressionDelegate stopObservingProgress:_webView];
}
}

- (UIView*)view {
return _webView;
}
Expand Down Expand Up @@ -323,6 +331,13 @@ - (NSString*)applySettings:(NSDictionary<NSString*, id>*)settings {
} else if ([key isEqualToString:@"hasNavigationDelegate"]) {
NSNumber* hasDartNavigationDelegate = settings[key];
_navigationDelegate.hasDartNavigationDelegate = [hasDartNavigationDelegate boolValue];
} else if ([key isEqualToString:@"hasProgressTracking"]) {
NSNumber* hasProgressTrackingValue = settings[key];
bool hasProgressTracking = [hasProgressTrackingValue boolValue];
if (hasProgressTracking) {
_progressionDelegate = [[FLTWKProgressionDelegate alloc] initWithWebView:_webView
channel:_channel];
}
} else if ([key isEqualToString:@"debuggingEnabled"]) {
// no-op debugging is always enabled on iOS.
} else if ([key isEqualToString:@"gestureNavigationEnabled"]) {
Expand Down
11 changes: 10 additions & 1 deletion packages/webview_flutter/lib/platform_interface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ abstract class WebViewPlatformCallbacksHandler {
/// Invoked by [WebViewPlatformController] when a page has finished loading.
void onPageFinished(String url);

/// Invoked by [WebViewPlatformController] when a page is loading.
jeremie-movify marked this conversation as resolved.
Show resolved Hide resolved
/// /// Only works when [WebSettings.hasProgressTracking] is set to `true`.
void onProgress(int progress);

/// Report web resource loading error to the host application.
void onWebResourceError(WebResourceError error);
}
Expand Down Expand Up @@ -388,6 +392,7 @@ class WebSettings {
WebSettings({
this.javascriptMode,
this.hasNavigationDelegate,
this.hasProgressTracking,
this.debuggingEnabled,
this.gestureNavigationEnabled,
this.allowsInlineMediaPlayback,
Expand All @@ -400,6 +405,10 @@ class WebSettings {
/// Whether the [WebView] has a [NavigationDelegate] set.
final bool? hasNavigationDelegate;

/// Whether the [WebView] should track page loading progress.
jeremie-movify marked this conversation as resolved.
Show resolved Hide resolved
/// See also: [WebViewPlatformCallbacksHandler.onProgress] to get the progress.
final bool? hasProgressTracking;

/// Whether to enable the platform's webview content debugging tools.
///
/// See also: [WebView.debuggingEnabled].
Expand Down Expand Up @@ -427,7 +436,7 @@ class WebSettings {

@override
String toString() {
return 'WebSettings(javascriptMode: $javascriptMode, hasNavigationDelegate: $hasNavigationDelegate, debuggingEnabled: $debuggingEnabled, gestureNavigationEnabled: $gestureNavigationEnabled, userAgent: $userAgent, allowsInlineMediaPlayback: $allowsInlineMediaPlayback)';
return 'WebSettings(javascriptMode: $javascriptMode, hasNavigationDelegate: $hasNavigationDelegate, hasProgressTracking: $hasProgressTracking, debuggingEnabled: $debuggingEnabled, gestureNavigationEnabled: $gestureNavigationEnabled, userAgent: $userAgent, allowsInlineMediaPlayback: $allowsInlineMediaPlayback)';
}
}

Expand Down
4 changes: 4 additions & 0 deletions packages/webview_flutter/lib/src/webview_method_channel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ class MethodChannelWebViewPlatform implements WebViewPlatformController {
case 'onPageFinished':
_platformCallbacksHandler.onPageFinished(call.arguments['url']!);
return null;
case 'onProgress':
_platformCallbacksHandler.onProgress(call.arguments['progress']);
jeremie-movify marked this conversation as resolved.
Show resolved Hide resolved
return null;
case 'onPageStarted':
_platformCallbacksHandler.onPageStarted(call.arguments['url']!);
return null;
Expand Down Expand Up @@ -183,6 +186,7 @@ class MethodChannelWebViewPlatform implements WebViewPlatformController {

_addIfNonNull('jsMode', settings!.javascriptMode?.index);
_addIfNonNull('hasNavigationDelegate', settings.hasNavigationDelegate);
_addIfNonNull('hasProgressTracking', settings.hasProgressTracking);
_addIfNonNull('debuggingEnabled', settings.debuggingEnabled);
_addIfNonNull(
'gestureNavigationEnabled', settings.gestureNavigationEnabled);
Expand Down
20 changes: 20 additions & 0 deletions packages/webview_flutter/lib/webview_flutter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ typedef void PageStartedCallback(String url);
/// Signature for when a [WebView] has finished loading a page.
typedef void PageFinishedCallback(String url);

/// Signature for when a [WebView] is loading a page.
typedef void PageLoadingCallback(int progress);

/// Signature for when a [WebView] has failed to load a resource.
typedef void WebResourceErrorCallback(WebResourceError error);

Expand Down Expand Up @@ -217,6 +220,7 @@ class WebView extends StatefulWidget {
this.gestureRecognizers,
this.onPageStarted,
this.onPageFinished,
this.onProgress,
this.onWebResourceError,
this.debuggingEnabled = false,
this.gestureNavigationEnabled = false,
Expand Down Expand Up @@ -357,6 +361,9 @@ class WebView extends StatefulWidget {
/// [WebViewController.evaluateJavascript] can assume this.
final PageFinishedCallback? onPageFinished;

/// Invoked when a page is loading.
final PageLoadingCallback? onProgress;

/// Invoked when a web resource has failed to load.
///
/// This can be called for any resource (iframe, image, etc.), not just for
Expand Down Expand Up @@ -476,6 +483,7 @@ WebSettings _webSettingsFromWidget(WebView widget) {
return WebSettings(
javascriptMode: widget.javascriptMode,
hasNavigationDelegate: widget.navigationDelegate != null,
hasProgressTracking: widget.onProgress != null,
debuggingEnabled: widget.debuggingEnabled,
gestureNavigationEnabled: widget.gestureNavigationEnabled,
allowsInlineMediaPlayback: widget.allowsInlineMediaPlayback,
Expand All @@ -488,6 +496,7 @@ WebSettings _clearUnchangedWebSettings(
WebSettings currentValue, WebSettings newValue) {
assert(currentValue.javascriptMode != null);
assert(currentValue.hasNavigationDelegate != null);
assert(currentValue.hasProgressTracking != null);
assert(currentValue.debuggingEnabled != null);
assert(currentValue.userAgent != null);
assert(newValue.javascriptMode != null);
Expand All @@ -497,6 +506,7 @@ WebSettings _clearUnchangedWebSettings(

JavascriptMode? javascriptMode;
bool? hasNavigationDelegate;
bool? hasProgressTracking;
bool? debuggingEnabled;
WebSetting<String?> userAgent = WebSetting.absent();
if (currentValue.javascriptMode != newValue.javascriptMode) {
Expand All @@ -505,6 +515,9 @@ WebSettings _clearUnchangedWebSettings(
if (currentValue.hasNavigationDelegate != newValue.hasNavigationDelegate) {
hasNavigationDelegate = newValue.hasNavigationDelegate;
}
if (currentValue.hasProgressTracking != newValue.hasProgressTracking) {
hasProgressTracking = newValue.hasProgressTracking;
}
if (currentValue.debuggingEnabled != newValue.debuggingEnabled) {
debuggingEnabled = newValue.debuggingEnabled;
}
Expand All @@ -515,6 +528,7 @@ WebSettings _clearUnchangedWebSettings(
return WebSettings(
javascriptMode: javascriptMode,
hasNavigationDelegate: hasNavigationDelegate,
hasProgressTracking: hasProgressTracking,
debuggingEnabled: debuggingEnabled,
userAgent: userAgent,
);
Expand Down Expand Up @@ -571,6 +585,12 @@ class _PlatformCallbacksHandler implements WebViewPlatformCallbacksHandler {
}

@override
void onProgress(int progress) {
if (_widget.onProgress != null) {
_widget.onProgress!(progress);
}
}

void onWebResourceError(WebResourceError error) {
if (_widget.onWebResourceError != null) {
_widget.onWebResourceError!(error);
Expand Down
68 changes: 68 additions & 0 deletions packages/webview_flutter/test/webview_flutter_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,62 @@ void main() {
});
});

group('$PageLoadingCallback', () {
testWidgets('onLoadingProgress is not null', (WidgetTester tester) async {
int? loadingProgress;

await tester.pumpWidget(WebView(
initialUrl: 'https://youtube.com',
onProgress: (int progress) {
loadingProgress = progress;
},
));

final FakePlatformWebView? platformWebView =
fakePlatformViewsController.lastCreatedView;

platformWebView?.fakeOnProgressCallback(50);

expect(loadingProgress, 50);
});

testWidgets('onLoadingProgress is null', (WidgetTester tester) async {
await tester.pumpWidget(const WebView(
initialUrl: 'https://youtube.com',
onProgress: null,
));

final FakePlatformWebView platformWebView =
fakePlatformViewsController.lastCreatedView!;

// This is to test that it does not crash on a null callback.
platformWebView.fakeOnProgressCallback(50);
});

testWidgets('onLoadingProgress changed', (WidgetTester tester) async {
int? loadingProgress;

await tester.pumpWidget(WebView(
initialUrl: 'https://youtube.com',
onProgress: (int progress) {},
));

await tester.pumpWidget(WebView(
initialUrl: 'https://youtube.com',
onProgress: (int progress) {
loadingProgress = progress;
},
));

final FakePlatformWebView platformWebView =
fakePlatformViewsController.lastCreatedView!;

platformWebView.fakeOnProgressCallback(50);

expect(loadingProgress, 50);
});
});

group('navigationDelegate', () {
testWidgets('hasNavigationDelegate', (WidgetTester tester) async {
await tester.pumpWidget(const WebView(
Expand Down Expand Up @@ -1021,6 +1077,18 @@ class FakePlatformWebView {
);
}

void fakeOnProgressCallback(int progress) {
final StandardMethodCodec codec = const StandardMethodCodec();

final ByteData data = codec.encodeMethodCall(MethodCall(
'onProgress',
<dynamic, dynamic>{'progress': progress},
));

ServicesBinding.instance!.defaultBinaryMessenger
.handlePlatformMessage(channel.name, data, (ByteData? data) {});
}

void _loadUrl(String? url) {
history = history.sublist(0, currentPosition + 1);
history.add(url);
Expand Down