Skip to content

Commit

Permalink
[webview_flutter] Add a page loaded callback (flutter#1335)
Browse files Browse the repository at this point in the history
  • Loading branch information
bparrishMines authored Mar 14, 2019
1 parent 6efa508 commit f30ef67
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@ public boolean shouldOverrideUrlLoading(WebView view, String url) {
return true;
}

@Override
public void onPageFinished(WebView view, String url) {
Map<String, Object> args = new HashMap<>();
args.put("url", url);
methodChannel.invokeMethod("onPageFinished", args);
}

private void notifyOnNavigationRequest(
String url, Map<String, String> headers, WebView webview, boolean isMainFrame) {
HashMap<String, Object> args = new HashMap<>();
Expand Down
5 changes: 4 additions & 1 deletion packages/webview_flutter/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class WebViewExample extends StatelessWidget {
// to allow calling Scaffold.of(context) so we can show a snackbar.
body: Builder(builder: (BuildContext context) {
return WebView(
initialUrl: 'https://flutter.io',
initialUrl: 'https://flutter.dev',
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
_controller.complete(webViewController);
Expand All @@ -61,6 +61,9 @@ class WebViewExample extends StatelessWidget {
print('allowing navigation to $request');
return NavigationDecision.navigate;
},
onPageFinished: (String url) {
print('Page finished loading: $url');
},
);
}),
floatingActionButton: favoriteButton(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,7 @@ - (void)webView:(WKWebView*)webView
}];
}

- (void)webView:(WKWebView*)webView didFinishNavigation:(WKNavigation*)navigation {
[_methodChannel invokeMethod:@"onPageFinished" arguments:@{@"url" : webView.URL.absoluteString}];
}
@end
31 changes: 30 additions & 1 deletion packages/webview_flutter/lib/webview_flutter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ enum NavigationDecision {
/// See also: [WebView.navigationDelegate].
typedef NavigationDecision NavigationDelegate(NavigationRequest navigation);

/// Signature for when a [WebView] has finished loading a page.
typedef void PageFinishedCallback(String url);

final RegExp _validChannelNames = RegExp('^[a-zA-Z_][a-zA-Z0-9]*\$');

/// A named channel for receiving messaged from JavaScript code running inside a web view.
Expand Down Expand Up @@ -113,6 +116,7 @@ class WebView extends StatefulWidget {
this.javascriptChannels,
this.navigationDelegate,
this.gestureRecognizers,
this.onPageFinished,
}) : assert(javascriptMode != null),
super(key: key);

Expand Down Expand Up @@ -189,6 +193,18 @@ class WebView extends StatefulWidget {
/// * When a navigationDelegate is set HTTP requests do not include the HTTP referer header.
final NavigationDelegate navigationDelegate;

/// Invoked when a page has finished loading.
///
/// This is invoked only for the main frame.
///
/// When [onPageFinished] is invoked on Android, the page being rendered may
/// not be updated yet.
///
/// When invoked on iOS or Android, any Javascript code that is embedded
/// directly in the HTML has been loaded and code injected with
/// [WebViewController.evaluateJavascript] can assume this.
final PageFinishedCallback onPageFinished;

@override
State<StatefulWidget> createState() => _WebViewState();
}
Expand Down Expand Up @@ -264,6 +280,7 @@ class _WebViewState extends State<WebView> {
_WebSettings.fromWidget(widget),
widget.javascriptChannels,
widget.navigationDelegate,
widget.onPageFinished,
);
_controller.complete(controller);
if (widget.onWebViewCreated != null) {
Expand Down Expand Up @@ -363,6 +380,7 @@ class WebViewController {
this._settings,
Set<JavascriptChannel> javascriptChannels,
this._navigationDelegate,
this._onPageFinished,
) : _channel = MethodChannel('plugins.flutter.io/webview_$id') {
_updateJavascriptChannelsFromSet(javascriptChannels);
_channel.setMethodCallHandler(_onMethodCall);
Expand All @@ -374,6 +392,8 @@ class WebViewController {

_WebSettings _settings;

final PageFinishedCallback _onPageFinished;

// Maps a channel name to a channel.
Map<String, JavascriptChannel> _javascriptChannels =
<String, JavascriptChannel>{};
Expand All @@ -391,13 +411,18 @@ class WebViewController {
url: call.arguments['url'],
isForMainFrame: call.arguments['isForMainFrame'],
);

// _navigationDelegate can be null if the widget was rebuilt with no
// navigation delegate after a navigation happened and just before we
// got the navigationRequest message.
final bool allowNavigation = _navigationDelegate == null ||
_navigationDelegate(request) == NavigationDecision.navigate;
return allowNavigation;
case 'onPageFinished':
if (_onPageFinished != null) {
_onPageFinished(call.arguments['url']);
}

return null;
}
throw MissingPluginException(
'${call.method} was invoked but has no handler');
Expand Down Expand Up @@ -560,6 +585,10 @@ class WebViewController {
///
/// The Future completes with an error if a JavaScript error occurred, or on iOS, if the type of the
/// evaluated expression is not supported as described above.
///
/// When evaluating Javascript in a [WebView], it is best practice to wait for
/// the [WebView.onPageFinished] callback. This guarantees all the Javascript
/// embedded in the main frame HTML has been loaded.
Future<String> evaluateJavascript(String javascriptString) async {
if (_settings.javascriptMode == JavascriptMode.disabled) {
throw FlutterError(
Expand Down
49 changes: 49 additions & 0 deletions packages/webview_flutter/test/webview_flutter_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,40 @@ void main() {
expect(ttsMessagesReceived, <String>['Hello', 'World']);
});

group('$PageFinishedCallback', () {
testWidgets('onPageFinished is not null', (WidgetTester tester) async {
String returnedUrl;

await tester.pumpWidget(WebView(
initialUrl: 'https://youtube.com',
onPageFinished: (String url) {
returnedUrl = url;
},
));

final FakePlatformWebView platformWebView =
fakePlatformViewsController.lastCreatedView;

platformWebView.fakeOnPageFinishedCallback();

expect(platformWebView.currentUrl, returnedUrl);
});

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

final FakePlatformWebView platformWebView =
fakePlatformViewsController.lastCreatedView;

// The platform side will always invoke a call for onPageFinished. This is
// to test that it does not crash on a null callback.
platformWebView.fakeOnPageFinishedCallback();
});
});

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

void fakeOnPageFinishedCallback() {
final StandardMethodCodec codec = const StandardMethodCodec();

final ByteData data = codec.encodeMethodCall(MethodCall(
'onPageFinished',
<dynamic, dynamic>{'url': currentUrl},
));

BinaryMessages.handlePlatformMessage(
channel.name,
data,
(ByteData data) {},
);
}

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

0 comments on commit f30ef67

Please sign in to comment.