Skip to content

Commit

Permalink
[url_launcher] Convert Windows to Pigeon (flutter#6991)
Browse files Browse the repository at this point in the history
* Initial definition matching current API

* Rename, autoformat

* Update native implementation and unit tests

* Update Dart; remove unnecessary Pigeon test API

* Version bump

* autoformat

* Adjust mock API setup

* Improve comment
  • Loading branch information
stuartmorgan authored and mauricioluz committed Jan 26, 2023
1 parent 9b66208 commit bdd082e
Show file tree
Hide file tree
Showing 16 changed files with 432 additions and 262 deletions.
3 changes: 2 additions & 1 deletion packages/url_launcher/url_launcher_windows/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## NEXT
## 3.0.3

* Converts internal implentation to Pigeon.
* Updates minimum Flutter version to 3.0.

## 3.0.2
Expand Down
71 changes: 71 additions & 0 deletions packages/url_launcher/url_launcher_windows/lib/src/messages.g.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// 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.
// Autogenerated from Pigeon (v5.0.1), do not edit directly.
// See also: https://pub.dev/packages/pigeon
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import
import 'dart:async';
import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;

import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
import 'package:flutter/services.dart';

class UrlLauncherApi {
/// Constructor for [UrlLauncherApi]. The [binaryMessenger] named argument is
/// available for dependency injection. If it is left null, the default
/// BinaryMessenger will be used which routes to the host platform.
UrlLauncherApi({BinaryMessenger? binaryMessenger})
: _binaryMessenger = binaryMessenger;
final BinaryMessenger? _binaryMessenger;

static const MessageCodec<Object?> codec = StandardMessageCodec();

Future<bool> canLaunchUrl(String arg_url) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.UrlLauncherApi.canLaunchUrl', codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList =
await channel.send(<Object?>[arg_url]) as List<Object?>?;
if (replyList == null) {
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
);
} else if (replyList.length > 1) {
throw PlatformException(
code: replyList[0]! as String,
message: replyList[1] as String?,
details: replyList[2],
);
} else if (replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (replyList[0] as bool?)!;
}
}

Future<void> launchUrl(String arg_url) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.UrlLauncherApi.launchUrl', codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList =
await channel.send(<Object?>[arg_url]) as List<Object?>?;
if (replyList == null) {
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
);
} else if (replyList.length > 1) {
throw PlatformException(
code: replyList[0]! as String,
message: replyList[1] as String?,
details: replyList[2],
);
} else {
return;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,21 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';

import 'package:flutter/services.dart';
import 'package:flutter/foundation.dart';
import 'package:url_launcher_platform_interface/link.dart';
import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart';

const MethodChannel _channel =
MethodChannel('plugins.flutter.io/url_launcher_windows');
import 'src/messages.g.dart';

/// An implementation of [UrlLauncherPlatform] for Windows.
class UrlLauncherWindows extends UrlLauncherPlatform {
/// Creates a new plugin implementation instance.
UrlLauncherWindows({
@visibleForTesting UrlLauncherApi? api,
}) : _hostApi = api ?? UrlLauncherApi();

final UrlLauncherApi _hostApi;

/// Registers this class as the default instance of [UrlLauncherPlatform].
static void registerWith() {
UrlLauncherPlatform.instance = UrlLauncherWindows();
Expand All @@ -23,10 +27,7 @@ class UrlLauncherWindows extends UrlLauncherPlatform {

@override
Future<bool> canLaunch(String url) {
return _channel.invokeMethod<bool>(
'canLaunch',
<String, Object>{'url': url},
).then((bool? value) => value ?? false);
return _hostApi.canLaunchUrl(url);
}

@override
Expand All @@ -39,16 +40,9 @@ class UrlLauncherWindows extends UrlLauncherPlatform {
required bool universalLinksOnly,
required Map<String, String> headers,
String? webOnlyWindowName,
}) {
return _channel.invokeMethod<bool>(
'launch',
<String, Object>{
'url': url,
'enableJavaScript': enableJavaScript,
'enableDomStorage': enableDomStorage,
'universalLinksOnly': universalLinksOnly,
'headers': headers,
},
).then((bool? value) => value ?? false);
}) async {
await _hostApi.launchUrl(url);
// Failure is handled via a PlatformException from `launchUrl`.
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
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.
18 changes: 18 additions & 0 deletions packages/url_launcher/url_launcher_windows/pigeons/messages.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// 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.

import 'package:pigeon/pigeon.dart';

@ConfigurePigeon(PigeonOptions(
dartOut: 'lib/src/messages.g.dart',
cppOptions: CppOptions(namespace: 'url_launcher_windows'),
cppHeaderOut: 'windows/messages.g.h',
cppSourceOut: 'windows/messages.g.cpp',
copyrightHeader: 'pigeons/copyright.txt',
))
@HostApi(dartHostTestHandler: 'TestUrlLauncherApi')
abstract class UrlLauncherApi {
bool canLaunchUrl(String url);
void launchUrl(String url);
}
3 changes: 2 additions & 1 deletion packages/url_launcher/url_launcher_windows/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: url_launcher_windows
description: Windows implementation of the url_launcher plugin.
repository: https://github.com/flutter/plugins/tree/main/packages/url_launcher/url_launcher_windows
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22
version: 3.0.2
version: 3.0.3

environment:
sdk: ">=2.12.0 <3.0.0"
Expand All @@ -24,4 +24,5 @@ dependencies:
dev_dependencies:
flutter_test:
sdk: flutter
pigeon: ^5.0.1
test: ^1.16.3
Original file line number Diff line number Diff line change
Expand Up @@ -5,140 +5,101 @@
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart';
import 'package:url_launcher_windows/src/messages.g.dart';
import 'package:url_launcher_windows/url_launcher_windows.dart';

void main() {
TestWidgetsFlutterBinding.ensureInitialized();

group('$UrlLauncherWindows', () {
const MethodChannel channel =
MethodChannel('plugins.flutter.io/url_launcher_windows');
final List<MethodCall> log = <MethodCall>[];
channel.setMockMethodCallHandler((MethodCall methodCall) async {
log.add(methodCall);

// Return null explicitly instead of relying on the implicit null
// returned by the method channel if no return statement is specified.
return null;
});
late _FakeUrlLauncherApi api;
late UrlLauncherWindows plugin;

test('registers instance', () {
UrlLauncherWindows.registerWith();
expect(UrlLauncherPlatform.instance, isA<UrlLauncherWindows>());
});
setUp(() {
api = _FakeUrlLauncherApi();
plugin = UrlLauncherWindows(api: api);
});

tearDown(() {
log.clear();
});
test('registers instance', () {
UrlLauncherWindows.registerWith();
expect(UrlLauncherPlatform.instance, isA<UrlLauncherWindows>());
});

test('canLaunch', () async {
final UrlLauncherWindows launcher = UrlLauncherWindows();
await launcher.canLaunch('http://example.com/');
expect(
log,
<Matcher>[
isMethodCall('canLaunch', arguments: <String, Object>{
'url': 'http://example.com/',
})
],
);
});
group('canLaunch', () {
test('handles true', () async {
api.canLaunch = true;

test('canLaunch should return false if platform returns null', () async {
final UrlLauncherWindows launcher = UrlLauncherWindows();
final bool canLaunch = await launcher.canLaunch('http://example.com/');
final bool result = await plugin.canLaunch('http://example.com/');

expect(canLaunch, false);
expect(result, isTrue);
expect(api.argument, 'http://example.com/');
});

test('launch', () async {
final UrlLauncherWindows launcher = UrlLauncherWindows();
await launcher.launch(
'http://example.com/',
useSafariVC: true,
useWebView: false,
enableJavaScript: false,
enableDomStorage: false,
universalLinksOnly: false,
headers: const <String, String>{},
);
expect(
log,
<Matcher>[
isMethodCall('launch', arguments: <String, Object>{
'url': 'http://example.com/',
'enableJavaScript': false,
'enableDomStorage': false,
'universalLinksOnly': false,
'headers': <String, String>{},
})
],
);
});
test('handles false', () async {
api.canLaunch = false;

test('launch with headers', () async {
final UrlLauncherWindows launcher = UrlLauncherWindows();
await launcher.launch(
'http://example.com/',
useSafariVC: true,
useWebView: false,
enableJavaScript: false,
enableDomStorage: false,
universalLinksOnly: false,
headers: const <String, String>{'key': 'value'},
);
expect(
log,
<Matcher>[
isMethodCall('launch', arguments: <String, Object>{
'url': 'http://example.com/',
'enableJavaScript': false,
'enableDomStorage': false,
'universalLinksOnly': false,
'headers': <String, String>{'key': 'value'},
})
],
);
final bool result = await plugin.canLaunch('http://example.com/');

expect(result, isFalse);
expect(api.argument, 'http://example.com/');
});
});

group('launch', () {
test('handles success', () async {
api.canLaunch = true;

test('launch universal links only', () async {
final UrlLauncherWindows launcher = UrlLauncherWindows();
await launcher.launch(
'http://example.com/',
useSafariVC: false,
useWebView: false,
enableJavaScript: false,
enableDomStorage: false,
universalLinksOnly: true,
headers: const <String, String>{},
);
expect(
log,
<Matcher>[
isMethodCall('launch', arguments: <String, Object>{
'url': 'http://example.com/',
'enableJavaScript': false,
'enableDomStorage': false,
'universalLinksOnly': true,
'headers': <String, String>{},
})
],
);
plugin.launch(
'http://example.com/',
useSafariVC: true,
useWebView: false,
enableJavaScript: false,
enableDomStorage: false,
universalLinksOnly: false,
headers: const <String, String>{},
),
completes);
expect(api.argument, 'http://example.com/');
});

test('launch should return false if platform returns null', () async {
final UrlLauncherWindows launcher = UrlLauncherWindows();
final bool launched = await launcher.launch(
'http://example.com/',
useSafariVC: true,
useWebView: false,
enableJavaScript: false,
enableDomStorage: false,
universalLinksOnly: false,
headers: const <String, String>{},
);

expect(launched, false);
test('handles failure', () async {
api.canLaunch = false;

await expectLater(
plugin.launch(
'http://example.com/',
useSafariVC: true,
useWebView: false,
enableJavaScript: false,
enableDomStorage: false,
universalLinksOnly: false,
headers: const <String, String>{},
),
throwsA(isA<PlatformException>()));
expect(api.argument, 'http://example.com/');
});
});
}

class _FakeUrlLauncherApi implements UrlLauncherApi {
/// The argument that was passed to an API call.
String? argument;

/// Controls the behavior of the fake implementations.
///
/// - [canLaunchUrl] returns this value.
/// - [launchUrl] throws if this is false.
bool canLaunch = false;

@override
Future<bool> canLaunchUrl(String url) async {
argument = url;
return canLaunch;
}

@override
Future<void> launchUrl(String url) async {
argument = url;
if (!canLaunch) {
throw PlatformException(code: 'Failed');
}
}
}
Loading

0 comments on commit bdd082e

Please sign in to comment.