[url_launcher] Move away from shared method channel implementation in native packages. (#4719)

* Move away from shared method channel implementation in native packages.

* Implement PR Feedback


Implement PR Feedback


Implement PR Feedback

* Fix missed test

* Implement PR feedback
diff --git a/packages/url_launcher/url_launcher_android/CHANGELOG.md b/packages/url_launcher/url_launcher_android/CHANGELOG.md
index 819b0cc..9ec1f65 100644
--- a/packages/url_launcher/url_launcher_android/CHANGELOG.md
+++ b/packages/url_launcher/url_launcher_android/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 6.0.15
+
+* Switches to an in-package method channel implementation.
+
 ## 6.0.14
 
 * Updates code for new analysis options.
diff --git a/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/MethodCallHandlerImpl.java b/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/MethodCallHandlerImpl.java
index 9e798ab..f7bed86 100644
--- a/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/MethodCallHandlerImpl.java
+++ b/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/MethodCallHandlerImpl.java
@@ -61,7 +61,7 @@
       stopListening();
     }
 
-    channel = new MethodChannel(messenger, "plugins.flutter.io/url_launcher");
+    channel = new MethodChannel(messenger, "plugins.flutter.io/url_launcher_android");
     channel.setMethodCallHandler(this);
   }
 
diff --git a/packages/url_launcher/url_launcher_android/android/src/test/java/io/flutter/plugins/urllauncher/MethodCallHandlerImplTest.java b/packages/url_launcher/url_launcher_android/android/src/test/java/io/flutter/plugins/urllauncher/MethodCallHandlerImplTest.java
index 5e08113..b601925 100644
--- a/packages/url_launcher/url_launcher_android/android/src/test/java/io/flutter/plugins/urllauncher/MethodCallHandlerImplTest.java
+++ b/packages/url_launcher/url_launcher_android/android/src/test/java/io/flutter/plugins/urllauncher/MethodCallHandlerImplTest.java
@@ -27,7 +27,7 @@
 
 @RunWith(RobolectricTestRunner.class)
 public class MethodCallHandlerImplTest {
-  private static final String CHANNEL_NAME = "plugins.flutter.io/url_launcher";
+  private static final String CHANNEL_NAME = "plugins.flutter.io/url_launcher_android";
   private UrlLauncher urlLauncher;
   private MethodCallHandlerImpl methodCallHandler;
 
diff --git a/packages/url_launcher/url_launcher_android/lib/url_launcher_android.dart b/packages/url_launcher/url_launcher_android/lib/url_launcher_android.dart
new file mode 100644
index 0000000..52c4635
--- /dev/null
+++ b/packages/url_launcher/url_launcher_android/lib/url_launcher_android.dart
@@ -0,0 +1,60 @@
+// 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 'dart:async';
+
+import 'package:flutter/services.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_android');
+
+/// An implementation of [UrlLauncherPlatform] for Android.
+class UrlLauncherAndroid extends UrlLauncherPlatform {
+  /// Registers this class as the default instance of [UrlLauncherPlatform].
+  static void registerWith() {
+    UrlLauncherPlatform.instance = UrlLauncherAndroid();
+  }
+
+  @override
+  final LinkDelegate? linkDelegate = null;
+
+  @override
+  Future<bool> canLaunch(String url) {
+    return _channel.invokeMethod<bool>(
+      'canLaunch',
+      <String, Object>{'url': url},
+    ).then((bool? value) => value ?? false);
+  }
+
+  @override
+  Future<void> closeWebView() {
+    return _channel.invokeMethod<void>('closeWebView');
+  }
+
+  @override
+  Future<bool> launch(
+    String url, {
+    required bool useSafariVC,
+    required bool useWebView,
+    required bool enableJavaScript,
+    required bool enableDomStorage,
+    required bool universalLinksOnly,
+    required Map<String, String> headers,
+    String? webOnlyWindowName,
+  }) {
+    return _channel.invokeMethod<bool>(
+      'launch',
+      <String, Object>{
+        'url': url,
+        'useWebView': useWebView,
+        'enableJavaScript': enableJavaScript,
+        'enableDomStorage': enableDomStorage,
+        'universalLinksOnly': universalLinksOnly,
+        'headers': headers,
+      },
+    ).then((bool? value) => value ?? false);
+  }
+}
diff --git a/packages/url_launcher/url_launcher_android/pubspec.yaml b/packages/url_launcher/url_launcher_android/pubspec.yaml
index ffd00e5..b8706ae 100644
--- a/packages/url_launcher/url_launcher_android/pubspec.yaml
+++ b/packages/url_launcher/url_launcher_android/pubspec.yaml
@@ -2,11 +2,11 @@
 description: Android implementation of the url_launcher plugin.
 repository: https://github.com/flutter/plugins/tree/main/packages/url_launcher/url_launcher_android
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22
-version: 6.0.14
+version: 6.0.15
 
 environment:
   sdk: ">=2.14.0 <3.0.0"
-  flutter: ">=2.5.0"
+  flutter: ">=2.8.0"
 
 flutter:
   plugin:
@@ -15,6 +15,7 @@
       android:
         package: io.flutter.plugins.urllauncher
         pluginClass: UrlLauncherPlugin
+        dartPluginClass: UrlLauncherAndroid
 
 dependencies:
   flutter:
diff --git a/packages/url_launcher/url_launcher_android/test/url_launcher_android_test.dart b/packages/url_launcher/url_launcher_android/test/url_launcher_android_test.dart
new file mode 100644
index 0000000..909d2c1
--- /dev/null
+++ b/packages/url_launcher/url_launcher_android/test/url_launcher_android_test.dart
@@ -0,0 +1,234 @@
+// 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:flutter/services.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:url_launcher_android/url_launcher_android.dart';
+import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart';
+
+void main() {
+  TestWidgetsFlutterBinding.ensureInitialized();
+
+  group('$UrlLauncherAndroid', () {
+    const MethodChannel channel =
+        MethodChannel('plugins.flutter.io/url_launcher_android');
+    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;
+    });
+
+    tearDown(() {
+      log.clear();
+    });
+
+    test('registers instance', () {
+      UrlLauncherAndroid.registerWith();
+      expect(UrlLauncherPlatform.instance, isA<UrlLauncherAndroid>());
+    });
+
+    test('canLaunch', () async {
+      final UrlLauncherAndroid launcher = UrlLauncherAndroid();
+      await launcher.canLaunch('http://example.com/');
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall('canLaunch', arguments: <String, Object>{
+            'url': 'http://example.com/',
+          })
+        ],
+      );
+    });
+
+    test('canLaunch should return false if platform returns null', () async {
+      final UrlLauncherAndroid launcher = UrlLauncherAndroid();
+      final bool canLaunch = await launcher.canLaunch('http://example.com/');
+
+      expect(canLaunch, false);
+    });
+
+    test('launch', () async {
+      final UrlLauncherAndroid launcher = UrlLauncherAndroid();
+      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/',
+            'useWebView': false,
+            'enableJavaScript': false,
+            'enableDomStorage': false,
+            'universalLinksOnly': false,
+            'headers': <String, String>{},
+          })
+        ],
+      );
+    });
+
+    test('launch with headers', () async {
+      final UrlLauncherAndroid launcher = UrlLauncherAndroid();
+      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/',
+            'useWebView': false,
+            'enableJavaScript': false,
+            'enableDomStorage': false,
+            'universalLinksOnly': false,
+            'headers': <String, String>{'key': 'value'},
+          })
+        ],
+      );
+    });
+
+    test('launch universal links only', () async {
+      final UrlLauncherAndroid launcher = UrlLauncherAndroid();
+      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/',
+            'useWebView': false,
+            'enableJavaScript': false,
+            'enableDomStorage': false,
+            'universalLinksOnly': true,
+            'headers': <String, String>{},
+          })
+        ],
+      );
+    });
+
+    test('launch force WebView', () async {
+      final UrlLauncherAndroid launcher = UrlLauncherAndroid();
+      await launcher.launch(
+        'http://example.com/',
+        useSafariVC: true,
+        useWebView: true,
+        enableJavaScript: false,
+        enableDomStorage: false,
+        universalLinksOnly: false,
+        headers: const <String, String>{},
+      );
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall('launch', arguments: <String, Object>{
+            'url': 'http://example.com/',
+            'useWebView': true,
+            'enableJavaScript': false,
+            'enableDomStorage': false,
+            'universalLinksOnly': false,
+            'headers': <String, String>{},
+          })
+        ],
+      );
+    });
+
+    test('launch force WebView enable javascript', () async {
+      final UrlLauncherAndroid launcher = UrlLauncherAndroid();
+      await launcher.launch(
+        'http://example.com/',
+        useSafariVC: true,
+        useWebView: true,
+        enableJavaScript: true,
+        enableDomStorage: false,
+        universalLinksOnly: false,
+        headers: const <String, String>{},
+      );
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall('launch', arguments: <String, Object>{
+            'url': 'http://example.com/',
+            'useWebView': true,
+            'enableJavaScript': true,
+            'enableDomStorage': false,
+            'universalLinksOnly': false,
+            'headers': <String, String>{},
+          })
+        ],
+      );
+    });
+
+    test('launch force WebView enable DOM storage', () async {
+      final UrlLauncherAndroid launcher = UrlLauncherAndroid();
+      await launcher.launch(
+        'http://example.com/',
+        useSafariVC: true,
+        useWebView: true,
+        enableJavaScript: false,
+        enableDomStorage: true,
+        universalLinksOnly: false,
+        headers: const <String, String>{},
+      );
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall('launch', arguments: <String, Object>{
+            'url': 'http://example.com/',
+            'useWebView': true,
+            'enableJavaScript': false,
+            'enableDomStorage': true,
+            'universalLinksOnly': false,
+            'headers': <String, String>{},
+          })
+        ],
+      );
+    });
+
+    test('launch should return false if platform returns null', () async {
+      final UrlLauncherAndroid launcher = UrlLauncherAndroid();
+      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('closeWebView default behavior', () async {
+      final UrlLauncherAndroid launcher = UrlLauncherAndroid();
+      await launcher.closeWebView();
+      expect(
+        log,
+        <Matcher>[isMethodCall('closeWebView', arguments: null)],
+      );
+    });
+  });
+}
diff --git a/packages/url_launcher/url_launcher_ios/CHANGELOG.md b/packages/url_launcher/url_launcher_ios/CHANGELOG.md
index 83d4e4e..6e0c8d6 100644
--- a/packages/url_launcher/url_launcher_ios/CHANGELOG.md
+++ b/packages/url_launcher/url_launcher_ios/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 6.0.15
+
+* Switches to an in-package method channel implementation.
+
 ## 6.0.14
 
 * Updates code for new analysis options.
diff --git a/packages/url_launcher/url_launcher_ios/ios/Classes/FLTURLLauncherPlugin.m b/packages/url_launcher/url_launcher_ios/ios/Classes/FLTURLLauncherPlugin.m
index 9ba9b13..1aceedc 100644
--- a/packages/url_launcher/url_launcher_ios/ios/Classes/FLTURLLauncherPlugin.m
+++ b/packages/url_launcher/url_launcher_ios/ios/Classes/FLTURLLauncherPlugin.m
@@ -63,7 +63,7 @@
 
 + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
   FlutterMethodChannel *channel =
-      [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/url_launcher"
+      [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/url_launcher_ios"
                                   binaryMessenger:registrar.messenger];
   FLTURLLauncherPlugin *plugin = [[FLTURLLauncherPlugin alloc] init];
   [registrar addMethodCallDelegate:plugin channel:channel];
diff --git a/packages/url_launcher/url_launcher_ios/lib/url_launcher_ios.dart b/packages/url_launcher/url_launcher_ios/lib/url_launcher_ios.dart
new file mode 100644
index 0000000..84b811b
--- /dev/null
+++ b/packages/url_launcher/url_launcher_ios/lib/url_launcher_ios.dart
@@ -0,0 +1,60 @@
+// 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 'dart:async';
+
+import 'package:flutter/services.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_ios');
+
+/// An implementation of [UrlLauncherPlatform] for iOS.
+class UrlLauncherIOS extends UrlLauncherPlatform {
+  /// Registers this class as the default instance of [UrlLauncherPlatform].
+  static void registerWith() {
+    UrlLauncherPlatform.instance = UrlLauncherIOS();
+  }
+
+  @override
+  final LinkDelegate? linkDelegate = null;
+
+  @override
+  Future<bool> canLaunch(String url) {
+    return _channel.invokeMethod<bool>(
+      'canLaunch',
+      <String, Object>{'url': url},
+    ).then((bool? value) => value ?? false);
+  }
+
+  @override
+  Future<void> closeWebView() {
+    return _channel.invokeMethod<void>('closeWebView');
+  }
+
+  @override
+  Future<bool> launch(
+    String url, {
+    required bool useSafariVC,
+    required bool useWebView,
+    required bool enableJavaScript,
+    required bool enableDomStorage,
+    required bool universalLinksOnly,
+    required Map<String, String> headers,
+    String? webOnlyWindowName,
+  }) {
+    return _channel.invokeMethod<bool>(
+      'launch',
+      <String, Object>{
+        'url': url,
+        'useSafariVC': useSafariVC,
+        'enableJavaScript': enableJavaScript,
+        'enableDomStorage': enableDomStorage,
+        'universalLinksOnly': universalLinksOnly,
+        'headers': headers,
+      },
+    ).then((bool? value) => value ?? false);
+  }
+}
diff --git a/packages/url_launcher/url_launcher_ios/pubspec.yaml b/packages/url_launcher/url_launcher_ios/pubspec.yaml
index a2e0ac2..8a5bfd2 100644
--- a/packages/url_launcher/url_launcher_ios/pubspec.yaml
+++ b/packages/url_launcher/url_launcher_ios/pubspec.yaml
@@ -2,11 +2,11 @@
 description: iOS implementation of the url_launcher plugin.
 repository: https://github.com/flutter/plugins/tree/main/packages/url_launcher/url_launcher_ios
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22
-version: 6.0.14
+version: 6.0.15
 
 environment:
   sdk: ">=2.14.0 <3.0.0"
-  flutter: ">=2.5.0"
+  flutter: ">=2.8.0"
 
 flutter:
   plugin:
@@ -14,6 +14,7 @@
     platforms:
       ios:
         pluginClass: FLTURLLauncherPlugin
+        dartPluginClass: UrlLauncherIOS
 
 dependencies:
   flutter:
diff --git a/packages/url_launcher/url_launcher_ios/test/url_launcher_ios_test.dart b/packages/url_launcher/url_launcher_ios/test/url_launcher_ios_test.dart
new file mode 100644
index 0000000..8fad580
--- /dev/null
+++ b/packages/url_launcher/url_launcher_ios/test/url_launcher_ios_test.dart
@@ -0,0 +1,208 @@
+// 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:flutter/services.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:url_launcher_ios/url_launcher_ios.dart';
+import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart';
+
+void main() {
+  TestWidgetsFlutterBinding.ensureInitialized();
+
+  group('$UrlLauncherIOS', () {
+    const MethodChannel channel =
+        MethodChannel('plugins.flutter.io/url_launcher_ios');
+    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;
+    });
+
+    tearDown(() {
+      log.clear();
+    });
+
+    test('registers instance', () {
+      UrlLauncherIOS.registerWith();
+      expect(UrlLauncherPlatform.instance, isA<UrlLauncherIOS>());
+    });
+
+    test('canLaunch', () async {
+      final UrlLauncherIOS launcher = UrlLauncherIOS();
+      await launcher.canLaunch('http://example.com/');
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall('canLaunch', arguments: <String, Object>{
+            'url': 'http://example.com/',
+          })
+        ],
+      );
+    });
+
+    test('canLaunch should return false if platform returns null', () async {
+      final UrlLauncherIOS launcher = UrlLauncherIOS();
+      final bool canLaunch = await launcher.canLaunch('http://example.com/');
+
+      expect(canLaunch, false);
+    });
+
+    test('launch', () async {
+      final UrlLauncherIOS launcher = UrlLauncherIOS();
+      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/',
+            'useSafariVC': true,
+            'enableJavaScript': false,
+            'enableDomStorage': false,
+            'universalLinksOnly': false,
+            'headers': <String, String>{},
+          })
+        ],
+      );
+    });
+
+    test('launch with headers', () async {
+      final UrlLauncherIOS launcher = UrlLauncherIOS();
+      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/',
+            'useSafariVC': true,
+            'enableJavaScript': false,
+            'enableDomStorage': false,
+            'universalLinksOnly': false,
+            'headers': <String, String>{'key': 'value'},
+          })
+        ],
+      );
+    });
+
+    test('launch force SafariVC', () async {
+      final UrlLauncherIOS launcher = UrlLauncherIOS();
+      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/',
+            'useSafariVC': true,
+            'enableJavaScript': false,
+            'enableDomStorage': false,
+            'universalLinksOnly': false,
+            'headers': <String, String>{},
+          })
+        ],
+      );
+    });
+
+    test('launch universal links only', () async {
+      final UrlLauncherIOS launcher = UrlLauncherIOS();
+      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/',
+            'useSafariVC': false,
+            'enableJavaScript': false,
+            'enableDomStorage': false,
+            'universalLinksOnly': true,
+            'headers': <String, String>{},
+          })
+        ],
+      );
+    });
+
+    test('launch force SafariVC to false', () async {
+      final UrlLauncherIOS launcher = UrlLauncherIOS();
+      await launcher.launch(
+        'http://example.com/',
+        useSafariVC: false,
+        useWebView: false,
+        enableJavaScript: false,
+        enableDomStorage: false,
+        universalLinksOnly: false,
+        headers: const <String, String>{},
+      );
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall('launch', arguments: <String, Object>{
+            'url': 'http://example.com/',
+            'useSafariVC': false,
+            'enableJavaScript': false,
+            'enableDomStorage': false,
+            'universalLinksOnly': false,
+            'headers': <String, String>{},
+          })
+        ],
+      );
+    });
+
+    test('launch should return false if platform returns null', () async {
+      final UrlLauncherIOS launcher = UrlLauncherIOS();
+      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('closeWebView default behavior', () async {
+      final UrlLauncherIOS launcher = UrlLauncherIOS();
+      await launcher.closeWebView();
+      expect(
+        log,
+        <Matcher>[isMethodCall('closeWebView', arguments: null)],
+      );
+    });
+  });
+}
diff --git a/packages/url_launcher/url_launcher_linux/CHANGELOG.md b/packages/url_launcher/url_launcher_linux/CHANGELOG.md
index a641f83..f346094 100644
--- a/packages/url_launcher/url_launcher_linux/CHANGELOG.md
+++ b/packages/url_launcher/url_launcher_linux/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.0.4
+
+* Switches to an in-package method channel implementation.
+
 ## 2.0.3
 
 * Updates code for new analysis options.
diff --git a/packages/url_launcher/url_launcher_linux/lib/url_launcher_linux.dart b/packages/url_launcher/url_launcher_linux/lib/url_launcher_linux.dart
new file mode 100644
index 0000000..87ef314
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/lib/url_launcher_linux.dart
@@ -0,0 +1,54 @@
+// 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 'dart:async';
+
+import 'package:flutter/services.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_linux');
+
+/// An implementation of [UrlLauncherPlatform] for Linux.
+class UrlLauncherLinux extends UrlLauncherPlatform {
+  /// Registers this class as the default instance of [UrlLauncherPlatform].
+  static void registerWith() {
+    UrlLauncherPlatform.instance = UrlLauncherLinux();
+  }
+
+  @override
+  final LinkDelegate? linkDelegate = null;
+
+  @override
+  Future<bool> canLaunch(String url) {
+    return _channel.invokeMethod<bool>(
+      'canLaunch',
+      <String, Object>{'url': url},
+    ).then((bool? value) => value ?? false);
+  }
+
+  @override
+  Future<bool> launch(
+    String url, {
+    required bool useSafariVC,
+    required bool useWebView,
+    required bool enableJavaScript,
+    required bool enableDomStorage,
+    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);
+  }
+}
diff --git a/packages/url_launcher/url_launcher_linux/linux/url_launcher_plugin.cc b/packages/url_launcher/url_launcher_linux/linux/url_launcher_plugin.cc
index d0f6168..b0c7fec 100644
--- a/packages/url_launcher/url_launcher_linux/linux/url_launcher_plugin.cc
+++ b/packages/url_launcher/url_launcher_linux/linux/url_launcher_plugin.cc
@@ -12,7 +12,7 @@
 #include "url_launcher_plugin_private.h"
 
 // See url_launcher_channel.dart for documentation.
-const char kChannelName[] = "plugins.flutter.io/url_launcher";
+const char kChannelName[] = "plugins.flutter.io/url_launcher_linux";
 const char kBadArgumentsError[] = "Bad Arguments";
 const char kLaunchError[] = "Launch Error";
 const char kCanLaunchMethod[] = "canLaunch";
diff --git a/packages/url_launcher/url_launcher_linux/pubspec.yaml b/packages/url_launcher/url_launcher_linux/pubspec.yaml
index 95b6c3c..ee55920 100644
--- a/packages/url_launcher/url_launcher_linux/pubspec.yaml
+++ b/packages/url_launcher/url_launcher_linux/pubspec.yaml
@@ -2,11 +2,11 @@
 description: Linux implementation of the url_launcher plugin.
 repository: https://github.com/flutter/plugins/tree/main/packages/url_launcher/url_launcher_linux
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22
-version: 2.0.3
+version: 2.0.4
 
 environment:
   sdk: ">=2.12.0 <3.0.0"
-  flutter: ">=2.0.0"
+  flutter: ">=2.5.0"
 
 flutter:
   plugin:
@@ -14,7 +14,14 @@
     platforms:
       linux:
         pluginClass: UrlLauncherPlugin
+        dartPluginClass: UrlLauncherLinux
 
 dependencies:
   flutter:
     sdk: flutter
+  url_launcher_platform_interface: ^2.0.3
+
+dev_dependencies:
+  flutter_test:
+    sdk: flutter
+  test: ^1.16.3
diff --git a/packages/url_launcher/url_launcher_linux/test/url_launcher_linux_test.dart b/packages/url_launcher/url_launcher_linux/test/url_launcher_linux_test.dart
new file mode 100644
index 0000000..7a4399d
--- /dev/null
+++ b/packages/url_launcher/url_launcher_linux/test/url_launcher_linux_test.dart
@@ -0,0 +1,144 @@
+// 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:flutter/services.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:url_launcher_linux/url_launcher_linux.dart';
+import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart';
+
+void main() {
+  TestWidgetsFlutterBinding.ensureInitialized();
+
+  group('$UrlLauncherLinux', () {
+    const MethodChannel channel =
+        MethodChannel('plugins.flutter.io/url_launcher_linux');
+    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;
+    });
+
+    tearDown(() {
+      log.clear();
+    });
+
+    test('registers instance', () {
+      UrlLauncherLinux.registerWith();
+      expect(UrlLauncherPlatform.instance, isA<UrlLauncherLinux>());
+    });
+
+    test('canLaunch', () async {
+      final UrlLauncherLinux launcher = UrlLauncherLinux();
+      await launcher.canLaunch('http://example.com/');
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall('canLaunch', arguments: <String, Object>{
+            'url': 'http://example.com/',
+          })
+        ],
+      );
+    });
+
+    test('canLaunch should return false if platform returns null', () async {
+      final UrlLauncherLinux launcher = UrlLauncherLinux();
+      final bool canLaunch = await launcher.canLaunch('http://example.com/');
+
+      expect(canLaunch, false);
+    });
+
+    test('launch', () async {
+      final UrlLauncherLinux launcher = UrlLauncherLinux();
+      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('launch with headers', () async {
+      final UrlLauncherLinux launcher = UrlLauncherLinux();
+      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'},
+          })
+        ],
+      );
+    });
+
+    test('launch universal links only', () async {
+      final UrlLauncherLinux launcher = UrlLauncherLinux();
+      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>{},
+          })
+        ],
+      );
+    });
+
+    test('launch should return false if platform returns null', () async {
+      final UrlLauncherLinux launcher = UrlLauncherLinux();
+      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);
+    });
+  });
+}
diff --git a/packages/url_launcher/url_launcher_macos/CHANGELOG.md b/packages/url_launcher/url_launcher_macos/CHANGELOG.md
index 9281c91..2bf9de9 100644
--- a/packages/url_launcher/url_launcher_macos/CHANGELOG.md
+++ b/packages/url_launcher/url_launcher_macos/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.0.4
+
+* Switches to an in-package method channel implementation.
+
 ## 2.0.3
 
 * Updates code for new analysis options.
diff --git a/packages/url_launcher/url_launcher_macos/lib/url_launcher_macos.dart b/packages/url_launcher/url_launcher_macos/lib/url_launcher_macos.dart
new file mode 100644
index 0000000..7dc1340
--- /dev/null
+++ b/packages/url_launcher/url_launcher_macos/lib/url_launcher_macos.dart
@@ -0,0 +1,54 @@
+// 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 'dart:async';
+
+import 'package:flutter/services.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_macos');
+
+/// An implementation of [UrlLauncherPlatform] for macOS.
+class UrlLauncherMacOS extends UrlLauncherPlatform {
+  /// Registers this class as the default instance of [UrlLauncherPlatform].
+  static void registerWith() {
+    UrlLauncherPlatform.instance = UrlLauncherMacOS();
+  }
+
+  @override
+  final LinkDelegate? linkDelegate = null;
+
+  @override
+  Future<bool> canLaunch(String url) {
+    return _channel.invokeMethod<bool>(
+      'canLaunch',
+      <String, Object>{'url': url},
+    ).then((bool? value) => value ?? false);
+  }
+
+  @override
+  Future<bool> launch(
+    String url, {
+    required bool useSafariVC,
+    required bool useWebView,
+    required bool enableJavaScript,
+    required bool enableDomStorage,
+    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);
+  }
+}
diff --git a/packages/url_launcher/url_launcher_macos/macos/Classes/UrlLauncherPlugin.swift b/packages/url_launcher/url_launcher_macos/macos/Classes/UrlLauncherPlugin.swift
index c311b85..4b799ee 100644
--- a/packages/url_launcher/url_launcher_macos/macos/Classes/UrlLauncherPlugin.swift
+++ b/packages/url_launcher/url_launcher_macos/macos/Classes/UrlLauncherPlugin.swift
@@ -36,7 +36,7 @@
 
   public static func register(with registrar: FlutterPluginRegistrar) {
     let channel = FlutterMethodChannel(
-      name: "plugins.flutter.io/url_launcher",
+      name: "plugins.flutter.io/url_launcher_macos",
       binaryMessenger: registrar.messenger)
     let instance = UrlLauncherPlugin()
     registrar.addMethodCallDelegate(instance, channel: channel)
diff --git a/packages/url_launcher/url_launcher_macos/pubspec.yaml b/packages/url_launcher/url_launcher_macos/pubspec.yaml
index e993bb6..0cd25d2 100644
--- a/packages/url_launcher/url_launcher_macos/pubspec.yaml
+++ b/packages/url_launcher/url_launcher_macos/pubspec.yaml
@@ -2,11 +2,11 @@
 description: macOS implementation of the url_launcher plugin.
 repository: https://github.com/flutter/plugins/tree/main/packages/url_launcher/url_launcher_macos
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22
-version: 2.0.3
+version: 2.0.4
 
 environment:
   sdk: ">=2.12.0 <3.0.0"
-  flutter: ">=2.0.0"
+  flutter: ">=2.5.0"
 
 flutter:
   plugin:
@@ -15,7 +15,14 @@
       macos:
         pluginClass: UrlLauncherPlugin
         fileName: url_launcher_macos.dart
+        dartPluginClass: UrlLauncherMacOS
 
 dependencies:
   flutter:
     sdk: flutter
+  url_launcher_platform_interface: ^2.0.3
+
+dev_dependencies:
+  flutter_test:
+    sdk: flutter
+  test: ^1.16.3
diff --git a/packages/url_launcher/url_launcher_macos/test/url_launcher_macos_test.dart b/packages/url_launcher/url_launcher_macos/test/url_launcher_macos_test.dart
new file mode 100644
index 0000000..0a28aea
--- /dev/null
+++ b/packages/url_launcher/url_launcher_macos/test/url_launcher_macos_test.dart
@@ -0,0 +1,144 @@
+// 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:flutter/services.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:url_launcher_macos/url_launcher_macos.dart';
+import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart';
+
+void main() {
+  TestWidgetsFlutterBinding.ensureInitialized();
+
+  group('$UrlLauncherMacOS', () {
+    const MethodChannel channel =
+        MethodChannel('plugins.flutter.io/url_launcher_macos');
+    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;
+    });
+
+    tearDown(() {
+      log.clear();
+    });
+
+    test('registers instance', () {
+      UrlLauncherMacOS.registerWith();
+      expect(UrlLauncherPlatform.instance, isA<UrlLauncherMacOS>());
+    });
+
+    test('canLaunch', () async {
+      final UrlLauncherMacOS launcher = UrlLauncherMacOS();
+      await launcher.canLaunch('http://example.com/');
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall('canLaunch', arguments: <String, Object>{
+            'url': 'http://example.com/',
+          })
+        ],
+      );
+    });
+
+    test('canLaunch should return false if platform returns null', () async {
+      final UrlLauncherMacOS launcher = UrlLauncherMacOS();
+      final bool canLaunch = await launcher.canLaunch('http://example.com/');
+
+      expect(canLaunch, false);
+    });
+
+    test('launch', () async {
+      final UrlLauncherMacOS launcher = UrlLauncherMacOS();
+      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('launch with headers', () async {
+      final UrlLauncherMacOS launcher = UrlLauncherMacOS();
+      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'},
+          })
+        ],
+      );
+    });
+
+    test('launch universal links only', () async {
+      final UrlLauncherMacOS launcher = UrlLauncherMacOS();
+      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>{},
+          })
+        ],
+      );
+    });
+
+    test('launch should return false if platform returns null', () async {
+      final UrlLauncherMacOS launcher = UrlLauncherMacOS();
+      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);
+    });
+  });
+}
diff --git a/packages/url_launcher/url_launcher_windows/CHANGELOG.md b/packages/url_launcher/url_launcher_windows/CHANGELOG.md
index e599b1f..a562da1 100644
--- a/packages/url_launcher/url_launcher_windows/CHANGELOG.md
+++ b/packages/url_launcher/url_launcher_windows/CHANGELOG.md
@@ -1,5 +1,6 @@
-## NEXT
+## 2.0.3
 
+* Switches to an in-package method channel implementation.
 * Adds unit tests.
 * Updates code for new analysis options.
 
diff --git a/packages/url_launcher/url_launcher_windows/lib/url_launcher_windows.dart b/packages/url_launcher/url_launcher_windows/lib/url_launcher_windows.dart
new file mode 100644
index 0000000..b0ee8cb
--- /dev/null
+++ b/packages/url_launcher/url_launcher_windows/lib/url_launcher_windows.dart
@@ -0,0 +1,54 @@
+// 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 'dart:async';
+
+import 'package:flutter/services.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');
+
+/// An implementation of [UrlLauncherPlatform] for Windows.
+class UrlLauncherWindows extends UrlLauncherPlatform {
+  /// Registers this class as the default instance of [UrlLauncherPlatform].
+  static void registerWith() {
+    UrlLauncherPlatform.instance = UrlLauncherWindows();
+  }
+
+  @override
+  final LinkDelegate? linkDelegate = null;
+
+  @override
+  Future<bool> canLaunch(String url) {
+    return _channel.invokeMethod<bool>(
+      'canLaunch',
+      <String, Object>{'url': url},
+    ).then((bool? value) => value ?? false);
+  }
+
+  @override
+  Future<bool> launch(
+    String url, {
+    required bool useSafariVC,
+    required bool useWebView,
+    required bool enableJavaScript,
+    required bool enableDomStorage,
+    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);
+  }
+}
diff --git a/packages/url_launcher/url_launcher_windows/pubspec.yaml b/packages/url_launcher/url_launcher_windows/pubspec.yaml
index b0acdab..506e995 100644
--- a/packages/url_launcher/url_launcher_windows/pubspec.yaml
+++ b/packages/url_launcher/url_launcher_windows/pubspec.yaml
@@ -2,11 +2,11 @@
 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: 2.0.2
+version: 2.0.3
 
 environment:
   sdk: ">=2.12.0 <3.0.0"
-  flutter: ">=2.0.0"
+  flutter: ">=2.5.0"
 
 flutter:
   plugin:
@@ -14,7 +14,14 @@
     platforms:
       windows:
         pluginClass: UrlLauncherWindows
+        dartPluginClass: UrlLauncherWindows
 
 dependencies:
   flutter:
     sdk: flutter
+  url_launcher_platform_interface: ^2.0.3
+
+dev_dependencies:
+  flutter_test:
+    sdk: flutter
+  test: ^1.16.3
diff --git a/packages/url_launcher/url_launcher_windows/test/url_launcher_windows_test.dart b/packages/url_launcher/url_launcher_windows/test/url_launcher_windows_test.dart
new file mode 100644
index 0000000..8b55b29
--- /dev/null
+++ b/packages/url_launcher/url_launcher_windows/test/url_launcher_windows_test.dart
@@ -0,0 +1,144 @@
+// 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: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/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;
+    });
+
+    test('registers instance', () {
+      UrlLauncherWindows.registerWith();
+      expect(UrlLauncherPlatform.instance, isA<UrlLauncherWindows>());
+    });
+
+    tearDown(() {
+      log.clear();
+    });
+
+    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/',
+          })
+        ],
+      );
+    });
+
+    test('canLaunch should return false if platform returns null', () async {
+      final UrlLauncherWindows launcher = UrlLauncherWindows();
+      final bool canLaunch = await launcher.canLaunch('http://example.com/');
+
+      expect(canLaunch, false);
+    });
+
+    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('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'},
+          })
+        ],
+      );
+    });
+
+    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>{},
+          })
+        ],
+      );
+    });
+
+    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);
+    });
+  });
+}
diff --git a/packages/url_launcher/url_launcher_windows/windows/url_launcher_plugin.cpp b/packages/url_launcher/url_launcher_windows/windows/url_launcher_plugin.cpp
index 748c75d..d5f2012 100644
--- a/packages/url_launcher/url_launcher_windows/windows/url_launcher_plugin.cpp
+++ b/packages/url_launcher/url_launcher_windows/windows/url_launcher_plugin.cpp
@@ -63,7 +63,7 @@
 void UrlLauncherPlugin::RegisterWithRegistrar(
     flutter::PluginRegistrar* registrar) {
   auto channel = std::make_unique<flutter::MethodChannel<>>(
-      registrar->messenger(), "plugins.flutter.io/url_launcher",
+      registrar->messenger(), "plugins.flutter.io/url_launcher_windows",
       &flutter::StandardMethodCodec::GetInstance());
 
   std::unique_ptr<UrlLauncherPlugin> plugin =