Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[webview_flutter] Adds support to listen to url changes #3313

Merged
merged 20 commits into from
Apr 18, 2023
Merged
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
4 changes: 3 additions & 1 deletion packages/webview_flutter/webview_flutter/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
## NEXT
## 4.1.0

* Adds support to track URL changes. See `NavigationDelegate(onUrlChange)`.
* Updates minimum Flutter version to 3.3.
* Fixes common typos in tests and documentation.
* Fixes documentation for `WebViewController` and `WebViewCookieManager`.

## 4.0.7

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,63 @@ Future<void> main() async {
final String? currentUrl = await controller.currentUrl();
expect(currentUrl, secondaryUrl);
});

testWidgets('can receive url changes', (WidgetTester tester) async {
final Completer<void> pageLoaded = Completer<void>();

final WebViewController controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setNavigationDelegate(NavigationDelegate(
onPageFinished: (_) => pageLoaded.complete(),
))
..loadRequest(Uri.parse(blankPageEncoded));

await tester.pumpWidget(WebViewWidget(controller: controller));

await pageLoaded.future;

final Completer<String> urlChangeCompleter = Completer<String>();
await controller.setNavigationDelegate(NavigationDelegate(
onUrlChange: (UrlChange change) {
urlChangeCompleter.complete(change.url);
},
));

await controller.runJavaScript('location.href = "$primaryUrl"');

await expectLater(urlChangeCompleter.future, completion(primaryUrl));
});

testWidgets('can receive updates to history state',
(WidgetTester tester) async {
final Completer<void> pageLoaded = Completer<void>();

final NavigationDelegate navigationDelegate = NavigationDelegate(
onPageFinished: (_) => pageLoaded.complete(),
);

final WebViewController controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setNavigationDelegate(navigationDelegate)
..loadRequest(Uri.parse(primaryUrl));

await tester.pumpWidget(WebViewWidget(controller: controller));

await pageLoaded.future;

final Completer<String> urlChangeCompleter = Completer<String>();
await controller.setNavigationDelegate(NavigationDelegate(
onUrlChange: (UrlChange change) {
urlChangeCompleter.complete(change.url);
},
));

await controller.runJavaScript(
'window.history.pushState({}, "", "secondary.txt");',
);

await expectLater(urlChangeCompleter.future, completion(secondaryUrl));
});
});

testWidgets('target _blank opens in same window',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ Page resource error:
debugPrint('allowing navigation to ${request.url}');
return NavigationDecision.navigate;
},
onUrlChange: (UrlChange change) {
debugPrint('url change to ${change.url}');
},
),
)
..addJavaScriptChannel(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ dependencies:
# The example app is bundled with the plugin so we use a path dependency on
# the parent directory to use the current plugin's version.
path: ../
webview_flutter_android: ^3.0.0
webview_flutter_wkwebview: ^3.0.0
webview_flutter_android: ^3.5.0
webview_flutter_wkwebview: ^3.3.0

dev_dependencies:
build_runner: ^2.1.5
Expand All @@ -29,7 +29,7 @@ dev_dependencies:
sdk: flutter
integration_test:
sdk: flutter
webview_flutter_platform_interface: ^2.0.0
webview_flutter_platform_interface: ^2.1.0

flutter:
uses-material-design: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,7 @@ class FakeNavigationDelegate extends PlatformNavigationDelegate {
Future<void> setOnWebResourceError(
WebResourceErrorCallback onWebResourceError,
) async {}

@override
Future<void> setOnUrlChange(UrlChangeCallback onUrlChange) async {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,20 +36,26 @@ import 'webview_controller.dart';
/// ```
class NavigationDelegate {
/// Constructs a [NavigationDelegate].
///
/// {@template webview_fluttter.navigation_delegate.constructor}
/// `onUrlChange`: invoked when the underlying web view changes to a new url.
/// {@endtemplate}
NavigationDelegate({
FutureOr<NavigationDecision> Function(NavigationRequest request)?
onNavigationRequest,
void Function(String url)? onPageStarted,
void Function(String url)? onPageFinished,
void Function(int progress)? onProgress,
void Function(WebResourceError error)? onWebResourceError,
void Function(UrlChange change)? onUrlChange,
}) : this.fromPlatformCreationParams(
const PlatformNavigationDelegateCreationParams(),
onNavigationRequest: onNavigationRequest,
onPageStarted: onPageStarted,
onPageFinished: onPageFinished,
onProgress: onProgress,
onWebResourceError: onWebResourceError,
onUrlChange: onUrlChange,
);

/// Constructs a [NavigationDelegate] from creation params for a specific
Expand Down Expand Up @@ -81,6 +87,8 @@ class NavigationDelegate {
/// );
/// ```
/// {@endtemplate}
///
/// {@macro webview_fluttter.navigation_delegate.constructor}
NavigationDelegate.fromPlatformCreationParams(
PlatformNavigationDelegateCreationParams params, {
FutureOr<NavigationDecision> Function(NavigationRequest request)?
Expand All @@ -89,23 +97,28 @@ class NavigationDelegate {
void Function(String url)? onPageFinished,
void Function(int progress)? onProgress,
void Function(WebResourceError error)? onWebResourceError,
void Function(UrlChange change)? onUrlChange,
}) : this.fromPlatform(
PlatformNavigationDelegate(params),
onNavigationRequest: onNavigationRequest,
onPageStarted: onPageStarted,
onPageFinished: onPageFinished,
onProgress: onProgress,
onWebResourceError: onWebResourceError,
onUrlChange: onUrlChange,
);

/// Constructs a [NavigationDelegate] from a specific platform implementation.
///
/// {@macro webview_fluttter.navigation_delegate.constructor}
NavigationDelegate.fromPlatform(
this.platform, {
this.onNavigationRequest,
this.onPageStarted,
this.onPageFinished,
this.onProgress,
this.onWebResourceError,
void Function(UrlChange change)? onUrlChange,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was not added as a field of the clas because there is a chance that the field can be different from the actual value. For example, a user may set this value and then call NavigationDelegate.platform.setOnUrlChange:

final NavigationDelegate delegate = NavigationDelegate(onUrlChange: (_) {});
delegate.platform.setUrlChange((_) {});

This is true for all the other callbacks, but changing them would be a breaking change.

Copy link
Contributor

Choose a reason for hiding this comment

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

This is true for all the other callbacks, but changing them would be a breaking change.

Couldn't we replace the final fields with getters, and have the getters return the platform version of the field, to preserve API compatibility while removing the potential for them to become out of sync?

(Doesn't need to be in this PR.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@mvanbeusekom suggested something similar by making the callback method setters in PlatformNavigationDelegate fields instead of methods. I decided against it initially because using a method allowed us to throw an UnimplementedError for unsupported callback methods. But we could add getters to the interface as a mix of both solutions.

This wouldn't be possible in this PR since it is something we would have to add to the platform interface first anyways.

}) {
if (onNavigationRequest != null) {
platform.setOnNavigationRequest(onNavigationRequest!);
Expand All @@ -122,6 +135,9 @@ class NavigationDelegate {
if (onWebResourceError != null) {
platform.setOnWebResourceError(onWebResourceError!);
}
if (onUrlChange != null) {
platform.setOnUrlChange(onUrlChange);
}
}

/// Implementation of [PlatformNavigationDelegate] for the current platform.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class WebViewController {
/// Constructs a [WebViewController] from creation params for a specific
/// platform.
///
/// {@template webview_flutter.WebViewCookieManager.fromPlatformCreationParams}
/// {@template webview_flutter.WebViewController.fromPlatformCreationParams}
/// Below is an example of setting platform-specific creation parameters for
/// iOS and Android:
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export 'package:webview_flutter_platform_interface/webview_flutter_platform_inte
PlatformWebViewCookieManagerCreationParams,
PlatformWebViewWidgetCreationParams,
ProgressCallback,
UrlChange,
WebResourceError,
WebResourceErrorCallback,
WebResourceErrorType,
Expand Down
4 changes: 2 additions & 2 deletions packages/webview_flutter/webview_flutter/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: webview_flutter
description: A Flutter plugin that provides a WebView widget on Android and iOS.
repository: https://github.com/flutter/packages/tree/main/packages/webview_flutter/webview_flutter
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22
version: 4.0.7
version: 4.1.0

environment:
sdk: ">=2.18.0 <4.0.0"
Expand All @@ -20,7 +20,7 @@ dependencies:
flutter:
sdk: flutter
webview_flutter_android: ^3.0.0
webview_flutter_platform_interface: ^2.0.0
webview_flutter_platform_interface: ^2.1.0
webview_flutter_wkwebview: ^3.0.0

dev_dependencies:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,18 @@ void main() {

verify(delegate.platform.setOnWebResourceError(onWebResourceError));
});

test('onUrlChange', () async {
WebViewPlatform.instance = TestWebViewPlatform();

void onUrlChange(UrlChange change) {}

final NavigationDelegate delegate = NavigationDelegate(
onUrlChange: onUrlChange,
);

verify(delegate.platform.setOnUrlChange(onUrlChange));
});
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ void main() {
main_file.WebViewCookie;
// ignore: unnecessary_statements
main_file.WebResourceErrorType;
// ignore: unnecessary_statements
main_file.UrlChange;
});
});
}